#include "my_http_request.h"
#include "my_http_session_pool.h"
#include "config/my_cache_conf.h"

// domain config
#define read_request_body_timeout                   1000
#define send_response_header_timeout_ms             1000
#define send_response_body_timeout                  1000
#define request_body_buf_size                       64 * 1024
#define request_body_bufs_size                      64 * 1024
#define enabled_lingering_close                     1
#define lingering_close_timeout                     1000
#define max_lingering_close_body                    64 * 1024
#define server_request_keepalive_timeout            60000
#define send_request_header_timeout_ms              1000
#define send_request_body_timeout                   1000
#define read_response_header_timeout_ms             1000
#define max_response_header_size                    8192
#define response_header_buf_size                    4096
#define read_response_body_timeout                  1000
#define response_body_buf_size                      64 * 1024
#define response_body_bufs_size                     64 * 1024
#define client_request_keepalive_timeout            64 * 1024

static void my_http_finalize_server_lingering_close(my_http_request_t *request,
    my_result_t res);
static void my_http_lingering_close_handler(my_event_t *event);

my_result_t
my_http_parse_header_line(my_str_t *line, my_http_header_t *header)
{
    char *p     = line->data;
    char *end   = p + line->len;
    char ch, c;

    if (p == end || *(end - 1) != LF) {
        return MY_HTTP_INVALID_HEADER_LINE;
    }

    // parse header name
    my_str_t header_name = MY_NULL_STRING;
    my_str_t header_value = MY_NULL_STRING;

    header_name.data = p;
    for (; p != end; p++) {
        ch = *p;

        if (ch >= '0' && ch <= '9') {
            continue;
        }

        c = ch | 0x20;
        if (c >= 'a' && c <= 'z') {
            continue;
        }

        if (ch == '-' || ch == '_') {
            continue;
        }

        if (ch == ':') {
            header_name.len = p - header_name.data;
            break;
        }

        // other char, eg: ' ', '.', '\r', '\n' ...
        return MY_HTTP_INVALID_HEADER_LINE;
    }

    if (header_name.len == 0) {
        return MY_HTTP_INVALID_HEADER_LINE;
    }

    // skip char ':'
    p++;

    // parse space before header value
    for (; p != end; p++) {
        ch = *p;
        if (ch != ' ') {
            break;
        }
    }

    // parse header value
    header_value.data = p;
    for (; p != end; p++) {
        ch = *p;
        if (ch == CR || ch == LF) {
            header_value.len = p - header_value.data;
            break;
        }
    }

    // trim header_value right space
    p = header_value.data + header_value.len - 1;
    end = header_value.data;
    for (; p != end; p--) {
        if (*p != ' ') {
            break;
        }
    }

    header_value.len = p + 1 - end;
    if (header_value.len == 0) {
        return MY_HTTP_INVALID_HEADER_LINE;
    }

    header->name = header_name;
    header->value = header_value;

    return MY_SUCCESS;
}


void
my_http_update_uri_ext(my_http_request_t *request)
{
    if (request->uri.len == 0) {
        request->uri_ext.data = NULL;
        request->uri_ext.len = 0;
    }

    int     i;
    char    ch;

    for (i = 0; i < request->uri.len; i++) {
        ch = *(request->uri.data + i);

        if (ch == '/') {
            request->uri_ext.data = NULL;
            continue;
        }

        if (ch == '.') {
            request->uri_ext.data = request->uri.data + i;
            continue;
        }
    }

    request->uri_ext.len =
        request->uri.data + request->uri.len - request->uri_ext.data;
}


static my_result_t
my_http_parse_request_uri(my_http_request_t *request, char **pos, char *end)
{
    char ch;

    if (*pos == end || **pos != '/' || *(end - 1) != LF) {
        return MY_HTTP_INVALID_REQUEST_URI;
    }

    // parse uri
    request->raw_uri.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;

        if (ch == CR || ch == LF || ch == ' ') {
            request->raw_uri.len = *pos - request->raw_uri.data;
            return MY_SUCCESS;
        }

        if (ch == '?') {
            request->raw_uri.len = *pos - request->raw_uri.data;
            break;
        }

        if (ch == '#') {
            request->raw_uri.len = *pos - request->raw_uri.data;
            goto FRAGMENT;
        }
    }

    // parse args
    (*pos)++;               /* skip char '?' */
    request->raw_args.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;

        if (ch == CR || ch == LF || ch == ' ') {
            request->raw_args.len = *pos - request->raw_args.data;
            return MY_SUCCESS;
        }

        if (ch == '#') {
            request->raw_args.len = *pos - request->raw_args.data;
            break;
        }
    }

    // parse fragment
FRAGMENT:
    (*pos)++;               /* skip char '#' */
    request->raw_fragment.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;

        if (ch == CR || ch == LF || ch == ' ') {
            request->raw_fragment.len = *pos - request->raw_fragment.data;
            return MY_SUCCESS;
        }
    }

    return MY_HTTP_INVALID_REQUEST_URI;
}


static my_result_t
my_http_parse_connect_request_uri(my_http_request_t *request, char **pos, char *end)
{
    char ch;

    if (*pos == end || **pos != 'h' || *(end - 1) != LF) {
        return MY_HTTP_INVALID_REQUEST_URI;
    }

    // parse connect scheme
    request->connect_scheme.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;

        if (ch == CR || ch == LF || ch == ' ') {
            return MY_HTTP_INVALID_REQUEST_URI;
        }

        if (ch == ':') {
            request->connect_scheme.len = *pos - request->connect_scheme.data;
            break;
        }
    }

    // parse '//' before connect host
    (*pos)++;                       /* skip char ':' */
    if (end - *pos < 2 || (*pos)[0] != '/' || (*pos)[1] != '/') {
        return MY_HTTP_INVALID_REQUEST_URI;
    }

    (*pos) += 2;

    // parse connect host
    ch = **pos;
    if (ch == '[') {
        (*pos)++;                   /* skip char '[' */
        request->connect_host.data = *pos;
        for (; *pos != end; (*pos)++) {
            ch = **pos;

            if (ch == CR || ch == LF || ch == ' ') {
                return MY_HTTP_INVALID_REQUEST_URI;
            }

            if (ch == ']') {
                request->connect_host.len = *pos - request->connect_host.data;
                break;
            }
        }

        (*pos)++;                   /* skip char ']' */
        ch = **pos;
        if (ch == '/') {
            goto URI;
        }

        if (ch != ':') {
            return MY_HTTP_INVALID_REQUEST_URI;
        }

    } else {
        request->connect_host.data = *pos;
        for (; *pos != end; (*pos)++) {
            ch = **pos;

            if (ch == CR || ch == LF || ch == ' ') {
                return MY_HTTP_INVALID_REQUEST_URI;
            }

            if (ch == '/') {
                request->connect_host.len = *pos - request->connect_host.data;
                goto URI;
            }

            if (ch == ':') {
                request->connect_host.len = *pos - request->connect_host.data;
                break;
            }
        }
    }

    // parse connect host
    (*pos)++;                   /* skip char ':' */
    request->connect_port.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;

        if (ch == CR || ch == LF || ch == ' ') {
            return MY_HTTP_INVALID_REQUEST_URI;
        }

        if (ch == '/') {
            request->connect_port.len = *pos - request->connect_port.data;
            break;
        }
    }

URI:
    return my_http_parse_request_uri(request, pos, end);
}


// support the following formats.
// HTTP VERSION == 0.9 request line format: [METHOD][ ][URI?ARGS#REF][ ][\r]\n
// [METHOD][ ][CONNECT_SCHEME://CONNECT_HOST:CONNECT_PORT][URI?ARGS#REF][ ][\r]\n
// HTTP VERSION >= 1.0 request line format: [METHOD][ ][URI?ARGS#REF][ ][HTTP/VERSION][\r]\n
// [METHOD][ ][CONNECT_SCHEME://CONNECT_HOST:CONNECT_PORT][URI?ARGS#REF][ ][HTTP/VERSION][\r]\n
my_result_t
my_http_parse_request_line(my_http_request_t *request)
{
    my_result_t res;
    char        *p;
    char        *end;

    p = request->readline_sm.line.data;
    end = p + request->readline_sm.line.len;

    if (p == end || *(end - 1) != LF) {
        return MY_HTTP_INVALID_REQUEST_LINE;
    }

    // parse request method
    request->method.data = p;
    for (; p != end; p++) {
        if (*p == ' ') {
            request->method.len = p - request->method.data;
            break;
        }

        if (*p == CR || *p == LF) {
            return MY_HTTP_INVALID_REQUEST_LINE;
        }
    }

    // parse space before request uri
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    // parse request uri
    if (*p == '/') {
        res = my_http_parse_request_uri(request, &p, end);
    } else if (*p == 'h') {
        res = my_http_parse_connect_request_uri(request, &p, end);
    } else {
        return MY_HTTP_INVALID_REQUEST_URI;
    }

    if (res != MY_SUCCESS) {
        return res;
    }

    // prase space before http version
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    my_str_t http_version_str;

    // parse http version
    http_version_str.data = p;
    for (; p != end; p++) {
        if (*p == CR || *p == LF || *p == ' ') {
            http_version_str.len = p - http_version_str.data;
            break;
        }
    }

    int http_major = 0;
    int http_minor = 0;

    if (http_version_str.len == 0) {
        request->http_version = MY_HTTP_VERSION_9;
    } else {
        if (http_version_str.len < (int64_t)MY_HTTP_VERSION_PERFIX_LEN ||
            strncmp(http_version_str.data,
                MY_HTTP_VERSION_PERFIX, MY_HTTP_VERSION_PERFIX_LEN) != 0) {
            return MY_HTTP_INVALID_HTTP_VERSION;
        }

        p = http_version_str.data + MY_HTTP_VERSION_PERFIX_LEN;
        end = http_version_str.data + http_version_str.len;

        while (p != end) {
            if (*p == '.') {
                break;
            }

            if (*p < '0' || *p > '9') {
                return MY_HTTP_INVALID_HTTP_VERSION;
            }

            http_major = http_major * 10 + (*p - '0');
            p++;
        }

        if (*p == '.' && p != end) {
            p++;
        }

        while (p != end) {
            if (*p < '0' || *p > '9') {
                return MY_HTTP_INVALID_HTTP_VERSION;
            }

            http_minor = http_minor * 10 + (*p - '0');
            p++;
        }

        request->http_version = http_major * 1000 + http_minor;
    }

    if (request->http_version != MY_HTTP_VERSION_11 &&
        request->http_version != MY_HTTP_VERSION_20 &&
        request->http_version != MY_HTTP_VERSION_QUIC &&
        request->http_version != MY_HTTP_VERSION_9  &&
        request->http_version != MY_HTTP_VERSION_10) {
        return MY_HTTP_INVALID_HTTP_VERSION;
    }

    if (request->connect_scheme.len > 0) {
        if (request->connect_scheme.len != MY_SCHEME_HTTP_LEN &&
            request->connect_scheme.len != MY_SCHEME_HTTPS_LEN) {
            return MY_HTTP_INVALID_CONNECT_SCHEME;
        }

        if (request->connect_scheme.len == MY_SCHEME_HTTP_LEN &&
            strncmp(request->connect_scheme.data,
                MY_SCHEME_HTTP, MY_SCHEME_HTTP_LEN) != 0) {
            return MY_HTTP_INVALID_CONNECT_SCHEME;
        }

        if (request->connect_scheme.len == MY_SCHEME_HTTPS_LEN &&
            strncmp(request->connect_scheme.data,
                MY_SCHEME_HTTPS, MY_SCHEME_HTTPS_LEN) != 0) {
            return MY_HTTP_INVALID_CONNECT_SCHEME;
        }
    }

    request->uri = request->raw_uri;
    my_http_update_uri_ext(request);
    request->args = request->raw_args;
    request->fragment = request->raw_fragment;

    return MY_SUCCESS;
}


static inline void
my_http_try_switch_request_header_buf(my_http_request_t *request, int64_t buf_size)
{
    my_buf_t                *header_buf;
    my_http_readline_sm_t   *readline_sm;

    header_buf = request->last_buf;
    readline_sm = &request->readline_sm;

    /* there is unparsed data, no need switch buf */
    if (header_buf && header_buf->end != header_buf->pos) {
        return;
    }

    /* avoid writeing in a small space */
    if (header_buf &&
        header_buf->data_end - header_buf->end > MY_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    header_buf = my_buf_alloc_ext(buf_size);
    request->last_buf = header_buf;
    my_buf_chain_enqueue(request->header_chain, header_buf);
    my_buf_free(header_buf);

    /* copy and reset readline sm */
    strncpy(header_buf->end, readline_sm->line.data, (size_t)readline_sm->line.len);
    header_buf->end += readline_sm->line.len;
    my_http_readline_sm_reset(readline_sm);
}


static my_result_t
my_http_do_read_request_header(my_event_t *event)
{
    my_conn_t               *c;
    my_http_request_t       *request;
    my_http_readline_sm_t   *readline_sm;
    int64_t                 n;
    my_result_t             res;
    my_syscall_result_t     sres;
    my_http_header_t        tmp;
    my_http_header_t        *header;
    my_buf_t                *header_buf;

    my_cache_conf_t         *cache_conf;
    int64_t                 request_header_buf_size;
    int64_t                 max_request_header_size;

    cache_conf = my_cache_conf_get();
    request_header_buf_size =
        my_conf_item_int64_get(&cache_conf->http_conf.request_header_buf_size);
    max_request_header_size =
        my_conf_item_int64_get(&cache_conf->http_conf.max_request_header_size);

    c = event->data;
    request = c->data;

    for ( ;; ) {
        /* big http request header */
        my_http_try_switch_request_header_buf(request, request_header_buf_size);

        /* no more data, read it */
        header_buf = request->last_buf;
        if (header_buf->pos == header_buf->end) {
            n = header_buf->data_end - header_buf->end;
            sres = my_conn_read(c, header_buf->end, n);

            /* wait read event callback */
            if (sres.eno == EAGAIN) {
                return MY_AGAIN;
            }

            if (sres.eno != 0) {
                MY_LOG_DEBUG("read request header failed, errno: %d", sres.eno);
                return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
            }

            if (sres.res == 0) {
                MY_LOG_DEBUG("clinet close write in read request header");
                return MY_EOS;
            }

            header_buf->end += sres.res;
        }

        readline_sm = &request->readline_sm;

        /* read line */
        res = my_http_readline(readline_sm, header_buf);
        if (res == MY_AGAIN) {
            continue;
        }

        if (res != MY_SUCCESS) {
            MY_LOG_DEBUG("invalid http request data, errcode: %d", res);
            return res;
        }

        request->header_size += readline_sm->line.len;
        if (request->header_size > max_request_header_size) {
            return MY_HTTP_REQUEST_HEADER_TOO_LARGER;
        }

        if (my_http_empty_line(readline_sm)) {
            if (!request->parse_rl_done) {
                return MY_HTTP_INVALID_HEADER_LINE;
            }

            request->parse_rh_done = 1;
            return MY_SUCCESS;
        }

        if (!request->parse_rl_done) {
            res = my_http_parse_request_line(request);
            if (res != MY_SUCCESS) {
                MY_LOG_DEBUG("parse request line failed, errcode: %d", res);
                return res;
            }

            request->parse_rl_done = 1;
            continue;
        }

        bzero(&tmp, sizeof(my_http_header_t));
        res = my_http_parse_header_line(&request->readline_sm.line, &tmp);
        if (res != MY_SUCCESS) {
            request->invalid_headers++;
            continue;
        }

        header = my_pool_mem_calloc(request->pool, sizeof(my_http_header_t));

        header->name = tmp.name;
        header->value = tmp.value;
        my_http_headers_add(&request->headers, header);
    }
}


static my_result_t
my_http_request_process_header(my_http_request_t *request)
{
    my_http_header_t *header;

    header = my_http_headers_get(&request->headers, &my_http_host_hdr_name);
    if (header == NULL && request->http_version > MY_HTTP_VERSION_10) {
        MY_LOG_DEBUG("no host header in http version %d request", request->http_version);
        return MY_HTTP_REQUIRE_HOST_HDR;
    }

    int64_t content_length = -1;
    /* if http request 1.0 has request body, there must has content-length header */
    header = my_http_headers_get(&request->headers, &my_http_cl_hdr_name);
    if (header != NULL) {
        if (!my_str_to_int64(&header->value, &content_length) || content_length < 0) {
            MY_LOG_DEBUG("invalid content length");
            return MY_HTTP_INVALID_CONTENT_LENGTH;
        }

        request->content_length = content_length;
        request->content_remain = content_length;
    }

    header = my_http_headers_get(&request->headers, &my_http_te_hdr_name);
    if (header != NULL) {
        if (header->value.len == MY_HTTP_CHUNKED_LEN &&
            strncasecmp(header->value.data, MY_HTTP_CHUNKED, MY_HTTP_CHUNKED_LEN) == 0) {
            request->chunked = 1;
        } else if (header->value.len != MY_HTTP_IDENTITY_LEN ||
            strncasecmp(header->value.data, MY_HTTP_IDENTITY, MY_HTTP_IDENTITY_LEN) != 0) {
            MY_LOG_DEBUG("invalid transfer-encoding: %v", &header->value);
            return MY_HTTP_INVALID_TRANSFER_ENCODING;
        }
    }

    if (request->http_version < MY_HTTP_VERSION_11) {
        request->want_close = 1;
    }

    header = my_http_headers_get(&request->headers, &my_http_connection_hdr_name);
    if (header != NULL) {
        if (request->http_version == MY_HTTP_VERSION_10 &&
            header->value.len == MY_HTTP_KEEPALIVE_LEN &&
            strncasecmp(header->value.data, MY_HTTP_KEEPALIVE, MY_HTTP_KEEPALIVE_LEN) == 0) {
            request->want_close = 0;
        }

        if (request->http_version >= MY_HTTP_VERSION_11 &&
            header->value.len == MY_HTTP_CLOSE_LEN &&
            strncasecmp(header->value.data, MY_HTTP_CLOSE, MY_HTTP_CLOSE_LEN) == 0) {
            request->want_close = 1;
        }
    }

    // no request body.
    if (!request->chunked && request->content_length < 0) {
        request->nobody = 1;
        request->done = 1;
    }

    // the request body or next http request data may be read when reading the request header.
    my_buf_t        *body_buf;
    body_buf = request->last_buf;
    if (body_buf && body_buf->pos != body_buf->end) {
        assert(request->body_buf == NULL);
        request->body_buf = my_buf_alloc(body_buf->data,
            body_buf->pos, body_buf->end);
    }

    request->last_buf = my_pool_mem_calloc(request->pool, sizeof(my_buf_t));
    if (body_buf) {
        // last_buf is the shadow of body_buf
        request->last_buf->pos = body_buf->pos;
    }

    return MY_SUCCESS;
}


static void
my_http_read_request_header_handler(my_event_t *event)
{
    my_conn_t           *c;
    my_http_request_t   *request;
    my_result_t         res;

    c = event->data;
    request = c->data;

    MY_LOG_DEBUG("http read request header handler callback");

    if (event->timedout) {
        MY_LOG_DEBUG("read request header timeout");
        event->timedout = 0;
        request->error = MY_TIMEOUT;
        /* disabled events callback */
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(c, MY_FALSE);
        request->handler(request, MY_TIMEOUT);
        return;
    }

    res = my_http_do_read_request_header(event);

    /* again, wait read event callback */
    if (res == MY_AGAIN) {
        return;
    }

    /* disabled events callback */
    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(c, MY_FALSE);

    if (res != MY_SUCCESS) {
        request->error = res;
        request->handler(request, res);
        return;
    }

    res = my_http_request_process_header(request);
    if (res != MY_SUCCESS) {
        request->error = res;
        request->handler(request, res);
        return;
    }

    request->handler(request, MY_SUCCESS);
}


void
my_http_start_read_request_header(my_http_request_t *request,
    my_http_request_handler_t done_handler, void *data)
{
    my_conn_t           *c;
    my_cache_conf_t     *cache_conf;
    int64_t             read_request_header_timeout_ms;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    if (request->start_read_header) {
        MY_LOG_FATAL("repeat call start_read_header");
        return;
    }

    if (request->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http request already finalish");
        return;
    }

    request->start_read_header = 1;

    request->handler = done_handler;
    request->data = data;

    c = request->conn;
    c->read.handler = my_http_read_request_header_handler;

    cache_conf = my_cache_conf_get();

    read_request_header_timeout_ms =
        my_conf_item_int64_get(&cache_conf->http_conf.read_request_header_timeout_ms);

    my_event_loop_add_timer(&c->read, read_request_header_timeout_ms);
    my_event_loop_enabled_io(c, MY_FALSE);

    my_http_read_request_header_handler(&c->read);
}


static my_result_t
my_http_read_after_body_line(my_http_request_t *request)
{
    char                *p;
    my_buf_t            *body_buf;
    my_conn_t           *conn;
    my_syscall_result_t sres;

    conn = request->conn;

    if (request->body_buf == NULL) {
        request->body_buf = my_buf_alloc_ext(CRLF_LEN);
    }

    if (request->body_buf->data_end - request->body_buf->pos < CRLF_LEN) {
        body_buf = my_buf_alloc_ext(CRLF_LEN);
        p = request->body_buf->pos;
        while (p != request->body_buf->end) {
            *request->body_buf->end ++ = *p ++;
        }

        my_buf_free(request->body_buf);
        request->body_buf = body_buf;
    }

    body_buf = request->body_buf;
    while (body_buf->end - body_buf->pos < CRLF_LEN) {
        sres = my_conn_read(conn, body_buf->end,
            CRLF_LEN - (body_buf->end - body_buf->pos));
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read after body line eagain");
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read after body line error, errno: %d", sres.res);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read after body line eos");
            return MY_EOS;
        }

        body_buf->end += sres.res;
    }

    if (body_buf->end - body_buf->pos < CRLF_LEN) {
        MY_LOG_FATAL("unreachable");
    }

    if (body_buf->pos[0] == CR && body_buf->pos[1] == LF) {
        body_buf->pos += CRLF_LEN;
        return MY_SUCCESS;
    }

    MY_LOG_DEBUG("read after body line failed, except %c%c, but got %c%c",
        CR, LF, body_buf->pos[0], body_buf->pos[1]);
    return MY_FAILED;
}


static void
my_http_try_switch_request_body_buf(my_http_request_t *request)
{
    my_buf_t    *body_buf;
    my_buf_t    *last_buf;
    my_buf_t    *tmp_buf;
    my_buf_t    *new_buf;
    int64_t     total_size;
    int64_t     n;

    body_buf = request->body_buf;
    last_buf = request->last_buf;

    /* there is unparsed data, no need switch buf */
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    /* avoid writeing in a small space */
    if (body_buf && body_buf->data_end - body_buf->end > MY_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    total_size = my_buf_chain_total_bytes(request->body_chain);
    if (total_size > request_body_bufs_size) {
        return;
    }

    new_buf = my_buf_alloc_ext(request_body_buf_size);

    if (body_buf) {
        if (last_buf == NULL ||
            last_buf->pos > body_buf->pos ||
            last_buf->pos < body_buf->start) {
            MY_LOG_FATAL("unreachable, the last_buf is the shadow of body_buf");
        }

        n =  body_buf->pos - last_buf->pos;
        if (n > 0) {
            tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
            my_buf_chain_enqueue(request->body_chain, tmp_buf);
            my_buf_free(tmp_buf);
        }

        my_buf_free(body_buf);
    }

    request->body_buf = new_buf;
    request->last_buf->pos = new_buf->pos;
}


static void
my_http_try_flush_request_body_buf(my_http_request_t *request)
{
    my_buf_t    *body_buf;
    my_buf_t    *last_buf;
    my_buf_t    *tmp_buf;
    int64_t     n;

    body_buf = request->body_buf;
    last_buf = request->last_buf;

    if (!body_buf) {
        return;
    }

    if (last_buf == NULL ||
        last_buf->pos > body_buf->pos ||
        last_buf->pos < body_buf->start) {
        MY_LOG_FATAL("unreachable, the last_buf is the shadow of body_buf");
    }

    n = body_buf->pos - last_buf->pos;

    if (n == 0) {
        return;
    }

    tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
    my_buf_chain_enqueue(request->body_chain, tmp_buf);
    my_buf_free(tmp_buf);

    last_buf->pos = body_buf->pos;
}


static my_result_t
my_http_read_request_body_in_cl_transfer(my_http_request_t *request)
{
    int64_t             n;
    int64_t             consum;
    my_conn_t           *conn;
    my_buf_t            *body_buf;
    my_syscall_result_t sres;

    conn = request->conn;

    for ( ;; ) {
        if (request->content_remain == 0) {
            if (!request->read_body_done) {
                MY_LOG_DEBUG("read request body done");
                request->read_body_done = 1;
                my_http_try_flush_request_body_buf(request);
            }

            return my_http_read_after_body_line(request);
        }

        my_http_try_switch_request_body_buf(request);

        body_buf = request->body_buf;
        n = body_buf->data_end - body_buf->end;

        if (n == 0) {
            MY_LOG_DEBUG("no enouge space to write request body");
            my_http_try_flush_request_body_buf(request);
            return MY_BUSY;
        }

        if (n > request->content_remain + CRLF_LEN) {
            n = request->content_remain + CRLF_LEN;
        }

        sres = my_conn_read(conn, body_buf->end, n);
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read request body eagain");
            my_http_try_flush_request_body_buf(request);
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read request body error, errno: %d", sres.eno);
            my_http_try_flush_request_body_buf(request);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read request body eos");
            my_http_try_flush_request_body_buf(request);
            return MY_EOS;
        }

        body_buf->end += sres.res;

        consum = sres.res;
        if (consum > request->content_remain) {
            consum = request->content_remain;
        }

        request->content_remain -= consum;
        request->body_buf->pos += consum;
    }
}


static void
my_http_try_switch_request_chunk_buf(my_http_request_t *request)
{
    my_buf_t                *body_buf;
    my_buf_t                *last_buf;
    my_buf_t                *tmp_buf;
    my_buf_t                *new_buf;
    my_http_readline_sm_t   *readline_sm;
    my_http_chunk_sm_t      *chunk_sm;
    int64_t                 total_size;
    int64_t                 n;

    body_buf = request->body_buf;
    last_buf = request->last_buf;
    readline_sm = &request->readline_sm;
    chunk_sm = request->chunk_sm;

    /* there is unparsed data, no need switch buf */
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    /* avoid writeing in a small space */
    if (body_buf && body_buf->data_end - body_buf->end > MY_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    total_size = my_buf_chain_total_bytes(request->body_chain);
    if (total_size > request_body_bufs_size) {
        return;
    }

    new_buf = my_buf_alloc_ext(request_body_buf_size);

    if (body_buf) {
        switch (chunk_sm->state) {
        case SW_CHUNK_PRESTART:
        case SW_CHUNK_AFTER_DATA_CRCF:
            /* copy and reset readline sm */
            strncpy(new_buf->end, readline_sm->line.data, (size_t)readline_sm->line.len);
            new_buf->end += readline_sm->line.len;
            my_http_readline_sm_reset(readline_sm);
            break;

        case SW_CHUNK_DATA:
            if (last_buf == NULL ||
                last_buf->pos > body_buf->pos ||
                last_buf->pos < body_buf->start) {
                    MY_LOG_FATAL("unreachable, the last_buf is the shadow of body_buf");
            }

            n = body_buf->pos - last_buf->pos;
            if (n > 0) {
                tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
                my_buf_chain_enqueue(request->body_chain, tmp_buf);
                my_buf_free(tmp_buf);
            }

            my_buf_free(body_buf);
            break;
        default:
            MY_LOG_FATAL("unreachable");
            break;
        }
    }

    request->body_buf = new_buf;
    if (chunk_sm->state == SW_CHUNK_DATA) {
        request->last_buf->pos = new_buf->pos;
    }
}


static void
my_http_try_flush_request_chunk_buf(my_http_request_t *request)
{
    my_buf_t                *body_buf;
    my_buf_t                *last_buf;
    my_buf_t                *tmp_buf;
    my_http_chunk_sm_t      *chunk_sm;
    int64_t                 n;

    body_buf = request->body_buf;
    last_buf = request->last_buf;
    chunk_sm = request->chunk_sm;

    if (!body_buf) {
        return;
    }

    if (chunk_sm->state != SW_CHUNK_DATA) {
        return;
    }

    if (last_buf == NULL ||
        last_buf->pos > body_buf->pos ||
        last_buf->pos < body_buf->start) {
        MY_LOG_FATAL("unreachable, the last_buf is the shadow of body buf");
    }

    n = body_buf->pos - last_buf->pos;

    if (n == 0) {
        return;
    }

    tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
    my_buf_chain_enqueue(request->body_chain, tmp_buf);
    my_buf_free(tmp_buf);

    last_buf->pos = body_buf->pos;
}


static my_result_t
my_http_read_request_body_in_chunked_transfer(my_http_request_t *request)
{
    my_conn_t               *conn;
    my_buf_t                *body_buf;
    my_http_chunk_sm_t      *chunk_sm;
    my_http_readline_sm_t   *readline_sm;
    my_result_t             res;
    my_syscall_result_t     sres;
    int64_t                 n, todo;
    my_http_chunk_state_t   state;

    // first call.
    if (!request->chunk_sm) {
        my_http_readline_sm_reset(&request->readline_sm);
        request->chunk_sm = my_pool_mem_calloc(request->pool, sizeof(my_http_chunk_sm_t));
    }

    conn = request->conn;
    chunk_sm = request->chunk_sm;
    readline_sm = &request->readline_sm;

    for ( ;; ) {
        my_http_try_switch_request_chunk_buf(request);

        body_buf = request->body_buf;
        n = body_buf->data_end - body_buf->end;
        if (n == 0) {
            MY_LOG_DEBUG("no enough space to write request body");
            my_http_try_flush_request_chunk_buf(request);
            return MY_BUSY;
        }

        sres = my_conn_read(conn, body_buf->end, n);
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read request body eagain");
            my_http_try_flush_request_chunk_buf(request);
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read request body error, errno: %d", sres.eno);
            my_http_try_flush_request_chunk_buf(request);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read request body eos");
            my_http_try_flush_request_chunk_buf(request);
            return MY_EOS;
        }

        body_buf->end += sres.res;

        for ( ;; ) {
            state = chunk_sm->state;

            switch (state) {
            case SW_CHUNK_PRESTART:
                res = my_http_readline(readline_sm, body_buf);
                break;
            case SW_CHUNK_START:
            case SW_CHUNK_SIZE:
                res = my_http_chunk_size_parse(chunk_sm, &readline_sm->line);
                break;
            case SW_CHUNK_DATA:
                /* consume data */
                n = body_buf->end - body_buf->pos;
                todo = chunk_sm->remain;
                if (n > todo) {
                    n = todo;
                }
                body_buf->pos += n;
                chunk_sm->remain -= n;

                res = MY_AGAIN;
                if (chunk_sm->remain == 0) {
                    my_http_try_flush_request_chunk_buf(request);
                    // to next step
                    chunk_sm->state = SW_CHUNK_AFTER_DATA_CRCF;
                    res = MY_SUCCESS;
                }

                break;
            case SW_CHUNK_AFTER_DATA_CRCF:
                res = my_http_readline(readline_sm, body_buf);
                break;
            default:
                MY_LOG_FATAL("unreachable");
                break;
            }

            if (res == MY_SUCCESS) {
                switch (state) {
                case SW_CHUNK_PRESTART:
                    my_http_chunk_sm_reset(chunk_sm);
                    chunk_sm->state = SW_CHUNK_START;
                    break;
                case SW_CHUNK_START:
                case SW_CHUNK_SIZE:
                    /* use last_buf to record the starting position of the data */
                    /* the last_buf is the shadow of body_buf */
                    request->last_buf->pos = request->body_buf->pos;
                    break;
                case SW_CHUNK_DATA:
                    my_http_readline_sm_reset(readline_sm);
                    break;
                case SW_CHUNK_AFTER_DATA_CRCF:
                    if (!my_http_empty_line(readline_sm)) {
                        return MY_HTTP_INVALID_CHUNK_LINE;
                    }

                    if (chunk_sm->size == 0) {
                        request->read_body_done = 1;
                        return MY_SUCCESS;
                    }

                    my_http_chunk_sm_reset(chunk_sm);
                    my_http_readline_sm_reset(readline_sm);
                    break;
                default:
                    MY_LOG_FATAL("unreachable");
                    break;
                }

                continue;
            }

            if (res == MY_AGAIN) {
                break;
            }

            /* error */
            return res;
        }
    }
}


static my_result_t
my_http_do_read_request_body(my_event_t *event)
{
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_result_t             res;

    conn = event->data;
    request = conn->data;

    if (!request->start_read_body) {
        MY_LOG_FATAL("unexcept stats");
    }

    if (event->timedout) {
        MY_LOG_DEBUG("read request body timedout");
        event->timedout = 0;
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_FALSE);

        // some client may not send the CRLF or chunk tailer after request body.
        // in this scene, we set want close to 1 to disabled keepalive.
        // we return success so that service can handle it normally.
        if (request->read_body_done) {
            request->error = MY_HTTP_READ_AFTER_BODY_DATA_TIMEOUT;
            request->want_close = 1;
            return MY_SUCCESS;
        }

        request->error = MY_HTTP_READ_REQUEST_BODY_TIMEOUT;
        return MY_TIMEOUT;
    }

    if (request->chunked) {
        res = my_http_read_request_body_in_chunked_transfer(request);
    } else if (request->content_length >= 0) {
        res = my_http_read_request_body_in_cl_transfer(request);
    } else {
        // no request body
        request->read_body_done = 1;
        res = MY_SUCCESS;
    }

    // disabled io / MY_SUCCESS/ MY_BUSY/ MY_EOS/ MY_FAILED.
    // although there are no disable events in the MY_AGAIN scenario, 
    // we still need to display call my_http_enabled_read_request_body,
    // because the upper state machine may call my_http_disabled_read_request_body
    // to close the event notification.
    if (res != MY_AGAIN) {
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_FALSE);
    }

    if (res == MY_EOS || res == MY_FAILED) {
        // some client may not send the CRLF or chunk tailer after request body.
        // in this scene, we set want close to 1 to disabled keepalive.
        // we return success so that service can handle it normally.
        if (request->read_body_done) {
            request->error = MY_HTTP_READ_AFTER_BODY_DATA_FAILED;
            request->want_close = 1;
            return MY_SUCCESS;
        }
    }

    return res;
}


static void
my_http_read_request_body_handler(my_event_t *event)
{
    my_result_t             res;
    my_conn_t               *conn;
    my_http_request_t       *request;

    conn = event->data;
    request = conn->data;

    res = my_http_do_read_request_body(event);

    /* in again scenario, no body data, no callback needed */
    if (res == MY_AGAIN &&
        my_buf_chain_total_bytes(request->body_chain) == 0) {
        my_http_enabled_read_request_body(request);
        return;
    }

    if (res == MY_SUCCESS && request->error == MY_SUCCESS) {
        request->done = 1;
    }

    request->handler(request, res);
}


void
my_http_start_read_request_body(my_http_request_t *request,
    my_http_request_handler_t notify_handler, void *data)
{
    my_conn_t       *conn;
    my_event_t      *event;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    if (!request->parse_rh_done) {
        MY_LOG_FATAL("header has no been parsed");
        return;
    }

    if (request->start_read_body) {
        MY_LOG_FATAL("repeat call start_read_request_body");
        return;
    }

    request->start_read_body = 1;

    if (request->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http request already finalish");
        return;
    }

    request->handler = notify_handler;
    request->data = data;

    conn = request->conn;
    event = &conn->read;

    if (request->nobody) {
        request->read_body_done = 1;
        request->handler(request, MY_SUCCESS);
        return;
    }

    event->handler = my_http_read_request_body_handler;
    my_event_loop_add_timer(event, read_request_body_timeout);
    my_event_loop_enabled_io(conn, MY_FALSE);

    my_http_read_request_body_handler(event);
}


void
my_http_enabled_read_request_body(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_event_t          *event;

    conn = request->conn;
    event = &conn->read;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    if (!request->start_read_body) {
        MY_LOG_FATAL("must exec start read request body first");
        return;
    }

    if (request->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http request already finalish");
        return;
    }

    my_event_loop_add_timer(event, read_request_body_timeout);
    my_event_loop_enabled_io(conn, MY_FALSE);

    if (event->ready) {
        event->handler(event);
    }
}


void
my_http_disabled_read_request_body(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_event_t          *event;

    conn = request->conn;
    event = &conn->read;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    if (!request->start_read_body) {
        MY_LOG_FATAL("must exec start read request body first");
    }

    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_FALSE);
}


// see RFC 7230, section 3.3.
static my_bool_t
my_http_body_allowed_for_status(int status_code)
{
    if (status_code >= 100 && status_code <= 199) {
        return MY_FALSE;
    }

    if (status_code == 204) {
        return MY_FALSE;
    }

    if (status_code == 304) {
        return MY_FALSE;
    }

    return MY_TRUE;
}


static my_result_t
my_http_seal_response_header(my_http_request_t *request)
{
    my_http_response_t      *response;
    my_buf_t                *header_buf;
    my_str_t                *http_version_str;
    my_http_header_t        *header;
    my_queue_t              *q;
    int64_t                 n = 0;
    int64_t                 m = 0;

    response = request->response;
    if (response->seal_header) {
        MY_LOG_FATAL("response header already sealed");
        return MY_FAILED;
    }

    response->seal_header = 1;

    if (response->status_code < MY_HTTP_CONTINUE &&
        response->status_code > MY_HTTP_STATUS_CODE_END) {
        MY_LOG_ERROR("invalid response status code: %d", response->status_code);
        return MY_HTTP_INVALID_RESPONSE_STATUS_CODE;
    }

    // 4xx, 5xx disable keepalive.
    if (response->status_code >= MY_HTTP_BAD_REQUEST) {
        request->want_close = 1;
    }

    if (my_http_method_is_head(request->method)) {
        response->nobody = 1;
    }

    if (!my_http_body_allowed_for_status(response->status_code)) {
        response->nobody = 1;
    }

    if (!response->reason) {
        // never nil
        response->reason = my_http_get_response_reason(response->status_code);
    }

    if (request->http_version == MY_HTTP_VERSION_20) {
#if 1
        MY_LOG_FATAL("no implements");
        return MY_FAILED;
#else
        return my_h2_seal_response_header(request);
#endif
    } else if (request->http_version == MY_HTTP_VERSION_QUIC) {
#if 1
        MY_LOG_FATAL("no implements");
        return MY_FAILED;
#else
        return my_quic_seal_response_header(request);
#endif
    } else if (request->http_version == MY_HTTP_VERSION_9) {
        // no response header in http version 9.
        response->chunked = 0;
        response->content_length = -1;
        request->want_close = 1;
        return MY_SUCCESS;
    } else if (request->http_version == MY_HTTP_VERSION_10) {
        http_version_str = &my_http_version_10_str;
        request->want_close = 1;
        my_http_headers_del_by_name(&response->headers, &my_http_te_hdr_name);
    } else {
        http_version_str = &my_http_version_11_str;
    }

    int64_t content_length = -1;
    header = my_http_headers_get(&response->headers, &my_http_cl_hdr_name);
    if (header != NULL) {
        if (!my_str_to_int64(&header->value, &content_length) || content_length < 0) {
            MY_LOG_ERROR("invalid content length header");
            return MY_HTTP_INVALID_CONTENT_LENGTH_HDR;
        }

        response->content_length = content_length;
        response->content_remain = content_length;
    }

    header = my_http_headers_get(&response->headers, &my_http_te_hdr_name);
    if (header != NULL) {
        if (header->value.len == MY_HTTP_CHUNKED_LEN &&
            strncasecmp(header->value.data, MY_HTTP_CHUNKED, MY_HTTP_CHUNKED_LEN) == 0) {
            response->chunked = 1;
        } else if (header->value.len != MY_HTTP_IDENTITY_LEN ||
            strncasecmp(header->value.data, MY_HTTP_IDENTITY, MY_HTTP_IDENTITY_LEN) == 0) {
            MY_LOG_ERROR("invalid transfer-encoding header");
            return MY_HTTP_INVALID_TE_HEADER;
        }
    }

    // if there is no Content-Length or chunked Transfer-Encoding on a response
    // and the status is not 1xx, 204 or 304, then the body is unbounded.
    // see RFC 7230, section 3.3.
    if (!response->nobody && response->content_length < 0 && !response->chunked) {
        // unbounded body.
        request->want_close = 1;
    }

    // HTTP_VERSION_STR + ' ' + STATUS_CODE + ' ' + HTTP_REASON + CRLF
    // eg: HTTP/1.1 200 OK\r\n
    n += http_version_str->len + 1 + 3 + 1 + response->reason->len + CRLF_LEN;
    // HEADER_NAME_STR + ':' + ' ' + HEADER_VALUE_STR + CRLF
    // eg: Content-Length: 100\r\n
    q = my_queue_head(&response->headers.headers);
    while (q != &response->headers.headers) {
        header = my_queue_data(q, my_http_header_t, queue_in_headers);
        n += header->name.len + 1 + 1 + header->value.len + CRLF_LEN;
        q = q->next;
    }
    // CRLF
    n += CRLF_LEN;

    header_buf = my_buf_alloc_ext(n);

    // copy response line.
    m = my_snprintf(header_buf->end, (size_t)(header_buf->data_end - header_buf->end),
        "%v %d %v\r\n", http_version_str, response->status_code, response->reason);
    header_buf->end += m;
#if 0
    n == http_version_str->len + 1 + 3 + 1 + response->reason->len + CRLF_LEN;
    assert(n == m);
#endif

    // copy response headers.
    q = my_queue_head(&response->headers.headers);
    while(q != &response->headers.headers) {
        header = my_queue_data(q, my_http_header_t, queue_in_headers);
        m = my_snprintf(header_buf->end, (size_t)(header_buf->data_end - header_buf->end),
            "%v: %v\r\n", &header->name, &header->value);
        header_buf->end += m;
#if 0
        n = header->name.len + 1 + 1 + header->value.len + CRLF_LEN;
        assert(n == m);
#endif
    }

    *(header_buf->end)++ = '\r';
    *(header_buf->end)++ = '\n';

    my_buf_chain_enqueue(response->header_chain, header_buf);
    my_buf_free(header_buf);

    return MY_SUCCESS;
}


static my_result_t
my_http_do_send_response_header(my_event_t *event)
{
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;
    my_buf_t                *header_buf;
    my_syscall_result_t     sres;
    int64_t                 todo = 0;
    int64_t                 n = 0;

    conn = event->data;
    request = conn->data;
    response = request->response;

    my_iovec_t              tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                     niov = 0;

    for ( ;; ) {
        todo = 0;
        niov = 0;

        header_buf = my_buf_chain_first(response->header_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && header_buf) {
            n = header_buf->end - header_buf->pos;
            if (n == 0) {
                header_buf = my_buf_chain_next(response->header_chain, header_buf);
                continue;
            }

            tiovec[niov].iov_base = header_buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            header_buf = my_buf_chain_next(response->header_chain, header_buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send response header eagain");
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send response header failed, errno: %d", sres.res);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("client close read in send response header");
            return MY_EOS;
        }

        my_buf_chain_consume(response->header_chain, sres.res);
    }

    todo = my_buf_chain_total_bytes(response->header_chain);
    if (todo != 0) {
        MY_LOG_FATAL("unreachable");
    }

    // release empty buf in header chain
    for ( ;; ) {
        header_buf = my_buf_chain_dequeue(response->header_chain);
        if (!header_buf) {
            break;
        }
        my_buf_free(header_buf);
    }

    return MY_SUCCESS;
}


static void
my_http_send_response_header_handler(my_event_t *event)
{
    my_result_t             res;
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;

    conn = event->data;
    request = conn->data;
    response = request->response;

    MY_LOG_DEBUG("http send response header handler callback");

    if (event->timedout) {
        MY_LOG_DEBUG("client timedout in send response header");
        event->timedout = 0;
        response->error = MY_TIMEOUT;
        /* disabled events callback */
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_TRUE);
        response->handler(request, MY_TIMEOUT);
        return;
    }

    res = my_http_do_send_response_header(event);

    /* again, wait write event callback */
    if (res == MY_AGAIN) {
        return;
    }

    /* disabled events callback */
    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_TRUE);

    if (res != MY_SUCCESS) {
        response->error = res;
        response->handler(request, res);
        return;
    }

    if (response->nobody) {
        response->done = 1;
    }

    response->send_header_done = 1;
    response->handler(request, MY_SUCCESS);
}


void
my_http_start_send_response_header(my_http_request_t *request,
    my_http_request_handler_t done_handler, void *data)
{
    my_conn_t               *c;
    my_http_response_t      *response;
    my_result_t             res;

    response = request->response;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    if (response->start_send_header) {
        MY_LOG_FATAL("repeat call start_send_header");
        return;
    }

    if (response->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    response->start_send_header = 1;

    response->handler = done_handler;
    response->data = data;

    res = my_http_seal_response_header(request);
    if (res != MY_SUCCESS) {
        response->error = res;
        response->handler(request, res);
        return;
    }

    c = request->conn;
    c->write.handler = my_http_send_response_header_handler;

    my_event_loop_add_timer(&c->write, send_response_header_timeout_ms);
    my_event_loop_enabled_io(c, MY_TRUE);

    my_http_send_response_header_handler(&c->write);
}


void
my_http_set_last_response_body_flag(my_http_request_t *request)
{
    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
    }

    request->response->last_body_flag = 1;
}


static void
my_http_response_body_move_in_chunked_transfer_internal(my_http_request_t *request)
{
    my_http_response_t      *response;
    my_buf_t                *buf;
    my_buf_t                *tmp_buf;
    int64_t                 todo = 0;
    int64_t                 n = 0;

    response = request->response;

    todo = my_buf_chain_total_bytes(response->body_chain);
    if (todo == 0) {
        return;
    }

    // last chunk buf already write output_chain.
    if (response->write_last_chunk_buf) {
        return;
    }

    // add chunked size buf before moveing response body.
    /* the "0000000000000000" is 64-bit hexadecimal string */
    tmp_buf = my_buf_alloc_ext(sizeof("0000000000000000" CRLF) - 1);
    n = my_snprintf(tmp_buf->pos, sizeof("0000000000000000" CRLF) - 1, "%X" CRLF, (size_t)todo);
    tmp_buf->end += n;

    my_buf_chain_enqueue(response->output_chain, tmp_buf);
    my_buf_free(tmp_buf);

    // move response body from response->body_chain to response->output_chain.
    buf = my_buf_chain_first(response->body_chain);
    while (buf != NULL) {
        n = buf->end - buf->pos;
        if (n == 0) {
            buf = my_buf_chain_next(response->body_chain, buf);
            continue;
        }

        tmp_buf = my_buf_alloc(buf->data, buf->pos, buf->end);
        my_buf_chain_enqueue(response->output_chain, tmp_buf);
        my_buf_free(tmp_buf);
        todo += n;
    }

    my_buf_chain_consume(response->body_chain, todo);
}


static void
my_http_response_body_move_in_chunked_transfer(my_http_request_t *request)
{
    my_http_response_t  *response;
    my_buf_t            *last_buf;
    int64_t             n = 0;

    response = request->response;

    my_http_response_body_move_in_chunked_transfer_internal(request);

    // body is not finished.
    if (!response->last_body_flag) {
        return;
    }

    // last chunk buf already write output_chain.
    if (response->write_last_chunk_buf) {
        return;
    }

    response->write_last_chunk_buf = 1;

    // append last chunk buf to output_chain
    /* the "0000000000000000" is 64-bit hexadecimal string */
    last_buf = my_buf_alloc_ext(sizeof("0000000000000000" CRLF) - 1);
    n = my_snprintf(last_buf->pos, sizeof("0000000000000000" CRLF) - 1, "%X" CRLF, 0);
    last_buf->end += n;
    my_buf_chain_enqueue(response->output_chain, last_buf);
    my_buf_free(last_buf);
}



static my_result_t
my_http_send_response_body_in_chunked_transfer(my_http_request_t *request)
{
    my_http_response_t      *response;
    my_buf_t                *buf;
    my_conn_t               *conn;
    my_syscall_result_t     sres;
    int64_t                 todo = 0;
    int64_t                 n = 0;
    int64_t                 m = 0;

    my_iovec_t              tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                     niov = 0;

    response = request->response;
    conn = request->conn;

    my_http_response_body_move_in_chunked_transfer(request);

    if (my_buf_chain_total_bytes(response->output_chain) == 0) {
        if (response->last_body_flag) {
            return MY_SUCCESS;
        }
        return MY_AGAIN;
    }

    m = 0;
    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = my_buf_chain_first(response->output_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && buf) {
            n = buf->end - buf->pos;
            if (n == 0) {
                buf = my_buf_chain_next(response->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            buf = my_buf_chain_next(response->output_chain, buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send response body eagain, m: %d", m);

            // if the write operation returns EAGAIN and no data is sent, then BUSY.
            if (m == 0) {
                return MY_BUSY;
            }

            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send response body failed, errno: %d", sres.res);
            return (my_result_t)(sres.res) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("client close read in send response body");
            return MY_EOS;
        }

        my_buf_chain_consume(response->output_chain, sres.res);
        m += sres.res;
    }

    if (response->last_body_flag) {
        return MY_SUCCESS;
    }

    return MY_AGAIN;
}


static my_result_t
my_http_send_response_body_in_cl_transfer(my_http_request_t *request)
{
    my_http_response_t      *response;
    my_buf_t                *buf;
    my_buf_t                *tmp_buf;
    my_conn_t               *conn;
    my_syscall_result_t     sres;
    int64_t                 todo = 0;
    int64_t                 n = 0;
    int64_t                 m = 0;

    my_iovec_t              tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                     niov = 0;

    response = request->response;
    conn = request->conn;

    // move response body from response->body_chain to response->output_chain.
    buf = my_buf_chain_first(response->body_chain);
    while (buf != NULL && response->content_remain > 0) {
        n = buf->end - buf->pos;
        if (n == 0) {
            buf = my_buf_chain_next(response->body_chain, buf);
            continue;
        }

        if (n > response->content_remain) {
            n = response->content_remain;
        }

        tmp_buf = my_buf_alloc(buf->data, buf->pos, buf->pos + n);
        my_buf_chain_enqueue(response->output_chain, tmp_buf);
        my_buf_free(tmp_buf);
        todo += n;
        response->content_remain -= n;
    }
    my_buf_chain_consume(response->body_chain, todo);

    if (my_buf_chain_total_bytes(response->output_chain) == 0) {
        if (response->content_remain == 0) {
            return MY_SUCCESS;
        }

        return MY_AGAIN;
    }

    m = 0;
    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = my_buf_chain_first(response->output_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && buf) {
            n = buf->end - buf->pos;
            if (n == 0) {
                buf = my_buf_chain_next(response->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            buf = my_buf_chain_next(response->output_chain, buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send response body eagain, m:%d", m);

            // if the write operation returns EAGAIN and no data is sent, then BUSY.
            if (m == 0) {
                return MY_BUSY;
            }

            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send response body failed, errno: %d", sres.res);
            return (my_result_t)(sres.res) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("client close read in send response body");
            return MY_EOS;
        }

        my_buf_chain_consume(response->output_chain, sres.res);
        m += sres.res;
    }

    // a body of the specified length has been sent.
    if (response->content_remain == 0) {
        return MY_SUCCESS;
    }

    return MY_AGAIN;
}


static my_result_t
my_http_send_response_in_unbounded_body_transfer(my_http_request_t *request)
{
    my_http_response_t      *response;
    my_buf_t                *buf;
    my_buf_t                *tmp_buf;
    my_conn_t               *conn;
    my_syscall_result_t     sres;
    int64_t                 todo = 0;
    int64_t                 n = 0;
    int64_t                 m = 0;

    my_iovec_t              tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                     niov = 0;

    response = request->response;
    conn = request->conn;

    // move response body from response->body_chain to response->output_chain.
    buf = my_buf_chain_first(response->body_chain);
    while (buf != NULL) {
        n = buf->end - buf->pos;
        if (n == 0) {
            buf = my_buf_chain_next(response->body_chain, buf);
            continue;
        }

        tmp_buf = my_buf_alloc(buf->data, buf->pos, buf->end);
        my_buf_chain_enqueue(response->output_chain, tmp_buf);
        my_buf_free(tmp_buf);
        todo += n;
    }
    my_buf_chain_consume(response->body_chain, todo);

    if (my_buf_chain_total_bytes(response->output_chain) == 0) {
        return MY_AGAIN;
    }

    m = 0;
    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = my_buf_chain_first(response->output_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && buf) {
            n = buf->end - buf->pos;
            if (n == 0) {
                buf = my_buf_chain_next(response->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            buf = my_buf_chain_next(response->output_chain, buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send response body eagain, m: %d", m);

            // if the write operation returns EAGAIN and no data is sent, then BUSY.
            if (m == 0) {
                return MY_BUSY;
            }

            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send response body failed, errno: %d", sres.res);
            return (my_result_t)(sres.res) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("client close read in send response body");
            return MY_EOS;
        }

        my_buf_chain_consume(response->output_chain, sres.res);
        m += sres.res;
    }

    return MY_AGAIN;
}


static my_result_t
my_http_send_response_body(my_http_request_t *request)
{
    my_http_response_t      *response;

    response = request->response;

    if (response->nobody) {
        return MY_SUCCESS;
    }

    if (response->chunked) {
        return my_http_send_response_body_in_chunked_transfer(request);
    }

    if (response->content_length >= 0) {
        return my_http_send_response_body_in_cl_transfer(request);
    }

    return my_http_send_response_in_unbounded_body_transfer(request);
}



static my_result_t
my_http_do_send_response_body(my_event_t *event)
{
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;
    my_result_t             res;

    conn = event->data;
    request = conn->data;
    response = request->response;

    if (!response->start_send_body) {
        MY_LOG_FATAL("unexcept stats");
    }

    if (event->timedout) {
        MY_LOG_DEBUG("send response body timeout");
        event->timedout = 0;
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_TRUE);
        response->error = MY_HTTP_SEND_RESPONSE_BODY_TIMEOUT;
        return MY_TIMEOUT;
    }

    if (request->http_version == MY_HTTP_VERSION_20) {
#if 1
        MY_LOG_FATAL("no implements");
        res = MY_FAILED;
#else
        res = my_h2_send_response_body(request);
#endif
    } else if (request->http_version == MY_HTTP_VERSION_QUIC) {
#if 1
        MY_LOG_FATAL("no implements");
        res = MY_FAILED;
#else
        res = my_quic_send_response_body(request);
#endif
    } else {
        res = my_http_send_response_body(request);
    }

    if (res == MY_BUSY) {
        return res;
    }

    if (res != MY_SUCCESS || res != MY_AGAIN) {
        response->error = res;
    }

    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_TRUE);

    return res;
}


static void
my_http_send_response_body_handler(my_event_t *event)
{
    my_result_t             res;
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;

    conn = event->data;
    request = conn->data;
    response = request->response;

    res = my_http_do_send_response_body(event);
    // wait write event callback
    if (res == MY_BUSY) {
        my_http_enabled_send_response_body(request);
        return;
    }

    if (res == MY_SUCCESS && response->error == MY_SUCCESS) {
        response->done = 1;
    }

    response->handler(request, res);
}


void
my_http_start_send_response_body(my_http_request_t *request,
    my_http_request_handler_t notify_handler, void *data)
{
    my_conn_t           *conn;
    my_event_t          *event;
    my_http_response_t  *response;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    response = request->response;

    if (!response->send_header_done) {
        MY_LOG_FATAL("response header not send");
        return;
    }

    if (response->start_send_body) {
        MY_LOG_FATAL("repeat call start_send_response_body");
        return;
    }

    response->start_send_body = 1;

    if (response->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    response->handler = notify_handler;
    response->data = data;

    // no response body
    if (response->nobody) {
        response->handler(request, MY_SUCCESS);
        return;
    }

    conn = request->conn;
    event = &conn->write;

    event->handler = my_http_send_response_body_handler;

    my_event_loop_add_timer(event, send_response_body_timeout);
    my_event_loop_enabled_io(conn, MY_TRUE);

    my_http_send_response_body_handler(event);
}


void
my_http_enabled_send_response_body(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_event_t          *event;
    my_http_response_t  *response;

    conn = request->conn;
    event = &conn->write;
    response = request->response;

    if (request->client_request) {
        MY_LOG_FATAL("current request is not http server request");
        return;
    }

    if (!response->start_send_body) {
        MY_LOG_FATAL("must exec start send response body first");
        return;
    }

    if (response->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    my_event_loop_add_timer(event, send_response_body_timeout);
    my_event_loop_enabled_io(conn, MY_TRUE);

    if (event->ready) {
        event->handler(event);
    }
}


static my_result_t
my_http_seal_request_header(my_http_request_t *request)
{
    my_http_header_t    *header;
    my_str_t            *http_version_str;
    my_buf_t            *header_buf;
    my_queue_t          *q;
    int64_t             n = 0;

    if (request->seal_header) {
        MY_LOG_FATAL("request header already sealed");
        return MY_FAILED;
    }

    request->seal_header = 1;

    if (request->method.len == 0) {
        MY_LOG_ERROR("invalid http request method");
        return MY_HTTP_INVALID_REQUEST_METHOD;
    }

    if (request->uri.len == 0 || *request->uri.data != '/') {
        MY_LOG_ERROR("invalid request uri: %v", &request->uri);
        return MY_HTTP_INVALID_REQUEST_URI;
    }

    if (request->http_version == MY_HTTP_VERSION_20) {
#if 1
        MY_LOG_FATAL("no implements");
        return MY_FAILED;
#else
        return my_h2_seal_request_header(request);
#endif
    } else if (request->http_version == MY_HTTP_VERSION_QUIC) {
#if 1
        MY_LOG_FATAL("no implements");
        return MY_FAILED;
#else
        return my_quic_seal_request_header(request);
#endif
    } else if (request->http_version == MY_HTTP_VERSION_9) {
        MY_LOG_FATAL("no implements");
        return MY_FAILED;
    } else if (request->http_version == MY_HTTP_VERSION_10) {
        request->want_close = 1;
        // chunked transport is not allowed in HTTP/1.0 scenarios
        my_http_headers_del_by_name(&request->headers, &my_http_te_hdr_name);
        http_version_str = &my_http_version_10_str;
    } else {
        // default, we use HTTP/1.1
        request->http_version = MY_HTTP_VERSION_11;
        http_version_str = &my_http_version_11_str;
    }

    header = my_http_headers_get(&request->headers, &my_http_connection_hdr_name);
    if (header != NULL) {
        if (request->http_version == MY_HTTP_VERSION_10 &&
            header->value.len == MY_HTTP_KEEPALIVE_LEN &&
            strncasecmp(header->value.data, MY_HTTP_KEEPALIVE, MY_HTTP_KEEPALIVE_LEN) == 0) {
            request->want_close = 0;
        }

        if (request->http_version >= MY_HTTP_VERSION_11 &&
            header->value.len == MY_HTTP_CLOSE_LEN &&
            strncasecmp(header->value.data, MY_HTTP_CLOSE, MY_HTTP_CLOSE_LEN) == 0) {
            request->want_close = 1;
        }
    }

    int64_t content_length = -1;
    header = my_http_headers_get(&request->headers, &my_http_cl_hdr_name);
    if (header != NULL) {
        if (!my_str_to_int64(&header->value, &content_length) || content_length < 0) {
            MY_LOG_ERROR("invalid content length header");
            return MY_HTTP_INVALID_CONTENT_LENGTH_HDR;
        }

        request->content_length = content_length;
        request->content_remain = content_length;
    }

    header = my_http_headers_get(&request->headers, &my_http_te_hdr_name);
    if (header != NULL) {
        if (header->value.len == MY_HTTP_CHUNKED_LEN &&
            strncasecmp(header->value.data, MY_HTTP_CHUNKED, MY_HTTP_CHUNKED_LEN) == 0) {
            request->chunked = 1;
        } else if (header->value.len == MY_HTTP_IDENTITY_LEN &&
            strncasecmp(header->value.data, MY_HTTP_IDENTITY, MY_HTTP_IDENTITY_LEN) == 0) {
            MY_LOG_ERROR("invalid transfer-encoding header");
            return MY_HTTP_INVALID_TE_HEADER;
        }
    }

    if (!request->chunked && request->content_length < 0) {
        request->nobody = 1;
    }

    // build request line
    // [METHOD][ ][CONNECT_SCHEME://CONNECT_HOST:CONNECT_PORT][URI?ARGS#REF][ ][HTTP/VERSION][\r]\n
    // [METHOD][ ]
    n = request->method.len + 1;
    // [CONNECT_SCHEME://CONNECT_HOST:CONNECT_PORT]
    // TODO: do not process the '['  and ']' in the ipv6 scenario.
    if (request->connect_scheme.len > 0 && request->connect_host.len > 0) {
        n = n + request->connect_scheme.len + 3 + request->connect_host.len;
        // :CONNECT_PORT
        if (request->connect_port.len > 0) {
            n = n + 1 + request->connect_port.len;
        }
    }
    // [URI]
    n += request->uri.len;
    // [?ARGS]
    if (request->args.len > 0) {
        n = n + 1 + request->args.len;
    }
    // [#REF]
    if (request->fragment.len > 0) {
        n = n + 1 + request->fragment.len;
    }
    // [ ][HTTP/VERSION][\r]\n
    n = n + 1 + http_version_str->len + CRLF_LEN;

    // HEADER_NAME_STR: HEADER_VALUE_STR\r\n
    q = my_queue_head(&request->headers.headers);
    while (q != &request->headers.headers) {
        header = my_queue_data(q, my_http_header_t, queue_in_headers);
        n += header->name.len + 1 + 1 + header->value.len + CRLF_LEN;
        q = q->next;
    }
    // EMPTY LINE
    n += CRLF_LEN;

    header_buf = my_buf_alloc_ext(n);

    // copu request line
    strncpy(header_buf->end, request->method.data, (size_t)request->method.len);
    header_buf->end += request->method.len;
    *(header_buf->end)++ = ' ';
    if (request->connect_scheme.len > 0 && request->connect_host.len > 0) {
        strncpy(header_buf->end, request->connect_scheme.data, (size_t)request->connect_scheme.len);
        header_buf->end += request->connect_scheme.len;
        *(header_buf->end)++ = ':';
        *(header_buf->end)++ = '/';
        *(header_buf->end)++ = '/';
        if (request->connect_port.len > 0) {
            *(header_buf->end)++ = ':';
            strncpy(header_buf->end, request->connect_port.data, (size_t)request->connect_port.len);
            header_buf->end += request->connect_port.len;
        }
    }
    strncpy(header_buf->end, request->uri.data, (size_t)request->uri.len);
    header_buf->end += request->uri.len;
    if (request->args.len > 0) {
        *(header_buf->end)++ = '?';
        strncpy(header_buf->end, request->args.data, (size_t)request->args.len);
        header_buf->end += request->args.len;
    }
    if (request->fragment.len > 0) {
        *(header_buf->end)++ = '#';
        strncpy(header_buf->end, request->fragment.data, (size_t)request->fragment.len);
        header_buf->end += request->fragment.len;
    }
    *(header_buf->end)++ = ' ';
    strncpy(header_buf->end, http_version_str->data, (size_t)http_version_str->len);
    header_buf->end += http_version_str->len;
    *(header_buf->end)++ = CR;
    *(header_buf->end)++ = LF;

    // copy request headers
    q = my_queue_head(&request->headers.headers);
    while (q != &request->headers.headers) {
        header = my_queue_data(q, my_http_header_t, queue_in_headers);
        n = my_snprintf(header_buf->end, (size_t)(header_buf->data_end - header_buf->end),
            "%v: %v\r\n", &header->name, &header->value);
        header_buf->end += n;
    }

    // copy empty line
    *(header_buf->end)++ = CR;
    *(header_buf->end)++ = LF;

    my_buf_chain_enqueue(request->header_chain, header_buf);
    my_buf_free(header_buf);

    return MY_SUCCESS;
}


static my_result_t
my_http_do_send_request_header(my_event_t *event)
{
    my_conn_t           *conn;
    my_http_request_t   *request;
    my_buf_t            *header_buf;
    my_syscall_result_t sres;
    int64_t             todo = 0;
    int64_t             n = 0;

    conn = event->data;
    request = conn->data;

    my_iovec_t          tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                 niov = 0;

    for ( ;; ) {
        todo = 0;
        niov = 0;

        header_buf = my_buf_chain_first(request->header_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && header_buf) {
            n = header_buf->end - header_buf->pos;
            if (n == 0) {
                header_buf = my_buf_chain_next(request->header_chain, header_buf);
                continue;
            }

            tiovec[niov].iov_base = header_buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            header_buf = my_buf_chain_next(request->header_chain, header_buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send request header eagain");
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send request header failed, errno: %d", sres.res);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("client close read in send request header");
            return MY_EOS;
        }

        my_buf_chain_consume(request->header_chain, sres.res);
    }

    todo = my_buf_chain_total_bytes(request->header_chain);
    if (todo != 0) {
        MY_LOG_FATAL("unreachable");
    }

    // release empty buf in header chain
    for ( ;; ) {
        header_buf = my_buf_chain_dequeue(request->header_chain);
        if (!header_buf) {
            break;
        }
        my_buf_free(header_buf);
    }

    return MY_SUCCESS;
}


static void
my_http_send_request_header_handler(my_event_t *event)
{
    my_result_t         res;
    my_conn_t           *conn;
    my_http_request_t   *request;

    conn = event->data;
    request = conn->data;

    MY_LOG_DEBUG("http send request header handler callback");

    if (event->timedout) {
        MY_LOG_DEBUG("client timedout in send request header");
        event->timedout = 0;
        request->error = MY_TIMEOUT;
        /* disabled events callback */
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_TRUE);
        request->handler(request, MY_TIMEOUT);
        return;
    }

    res = my_http_do_send_request_header(event);

    /* again, wait write event callback */
    if (res == MY_AGAIN) {
        return;
    }

    /* disabled events callback */
    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_TRUE);

    if (res != MY_SUCCESS) {
        request->error = res;
        request->handler(request, res);
        return;
    }

    if (request->nobody) {
        request->done = 1;
    }

    request->send_header_done = 1;
    request->handler(request, MY_SUCCESS);
}


void
my_http_start_send_request_header(my_http_request_t *request,
    my_http_request_handler_t done_handler, void *data)
{
    my_conn_t       *conn;
    my_event_t      *event;
    my_result_t     res;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    if (request->start_send_header) {
        MY_LOG_FATAL("repeat call start_send_header");
        return;
    }

    if (request->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http request already finalish");
        return;
    }

    request->start_send_header = 1;

    request->handler = done_handler;
    request->data = data;

    res = my_http_seal_request_header(request);
    if (res != MY_SUCCESS) {
        request->error = res;
        request->handler(request, res);
        return;
    }

    conn = request->conn;
    event = &conn->write;
    event->handler = my_http_send_request_header_handler;

    my_event_loop_add_timer(event, send_request_header_timeout_ms);
    my_event_loop_enabled_io(conn, MY_TRUE);

    my_http_send_request_header_handler(event);
}


void
my_http_set_last_request_body_flag(my_http_request_t *request)
{
    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    request->last_body_flag = 1;
}


static void
my_http_request_body_move_in_chunked_transfer_internal(my_http_request_t *request)
{
    my_buf_t            *buf;
    my_buf_t            *tmp_buf;
    int64_t             todo = 0;
    int64_t             n = 0;


    todo = my_buf_chain_total_bytes(request->body_chain);
    if (todo == 0) {
        return;
    }

    // add chunked size buf before moveing response body.
    /* the "0000000000000000" is 64-bit hexadecimal string */
    tmp_buf = my_buf_alloc_ext(sizeof("0000000000000000" CRLF) - 1);
    n = my_snprintf(tmp_buf->pos, sizeof("0000000000000000" CRLF) - 1, "%X" CRLF, (size_t)todo);
    tmp_buf->end += n;

    my_buf_chain_enqueue(request->output_chain, tmp_buf);
    my_buf_free(tmp_buf);

    // move request body from request->body_chain to request->output_chain.
    buf = my_buf_chain_first(request->body_chain);
    while (buf != NULL) {
        n = buf->end - buf->pos;
        if (n == 0) {
            buf = my_buf_chain_next(request->body_chain, buf);
            continue;
        }

        tmp_buf = my_buf_alloc(buf->data, buf->pos, buf->end);
        my_buf_chain_enqueue(request->output_chain, tmp_buf);
        my_buf_free(tmp_buf);
        todo += n;
    }

    my_buf_chain_consume(request->body_chain, todo);
}


static void
my_http_request_body_move_in_chunked_transfer(my_http_request_t *request)
{
    my_buf_t                *last_buf;
    int64_t                 n = 0;

    my_http_request_body_move_in_chunked_transfer_internal(request);

    // body is not finished.
    if (!request->last_body_flag) {
        return;
    }

    // last chunk buf already write output_chain
    if (request->write_last_chunk_buf) {
        return;
    }

    request->write_last_chunk_buf = 1;

    // append last chunk buf to output_chain
    /* the "0000000000000000" is 64-bit hexadecimal string */
    last_buf = my_buf_alloc_ext(sizeof("0000000000000000" CRLF) - 1);
    n = my_snprintf(last_buf->pos, sizeof("0000000000000000" CRLF) - 1, "%X" CRLF, 0);
    last_buf->end += n;
    my_buf_chain_enqueue(request->output_chain, last_buf);
    my_buf_free(last_buf);
}


static my_result_t
my_http_send_request_body_in_chunked_transfer(my_http_request_t *request)
{
    my_buf_t                *buf;
    my_conn_t               *conn;
    my_syscall_result_t     sres;
    int64_t                 todo = 0;
    int64_t                 n = 0;
    int64_t                 m = 0;

    my_iovec_t              tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                     niov = 0;

    conn = request->conn;

    my_http_request_body_move_in_chunked_transfer(request);

    if (my_buf_chain_total_bytes(request->output_chain) == 0) {
        if (request->last_body_flag) {
            return MY_SUCCESS;
        }
        return MY_AGAIN;
    }

    m = 0;
    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = my_buf_chain_first(request->output_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && buf) {
            n = buf->end - buf->pos;
            if (n == 0) {
                buf = my_buf_chain_next(request->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            buf = my_buf_chain_next(request->output_chain, buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send request body eagain, m: %d", m);

            // if the write operation returns EAGAIN and no data is sent, then BUSY.
            if (m == 0) {
                return MY_BUSY;
            }

            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send request body failed, errno: %d", sres.res);
            return (my_result_t)(sres.res) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("server close read in send response body");
            return MY_EOS;
        }

        my_buf_chain_consume(request->output_chain, sres.res);
        m += sres.res;
    }

    if (request->last_body_flag) {
        return MY_SUCCESS;
    }

    return MY_AGAIN;
}


static my_result_t
my_http_send_request_body_in_cl_transfer(my_http_request_t *request)
{
    my_buf_t            *buf;
    my_buf_t            *tmp_buf;
    my_conn_t           *conn;
    my_syscall_result_t sres;
    int64_t             todo = 0;
    int64_t             n = 0;
    int64_t             m = 0;

    my_iovec_t              tiovec[MY_HTTP_MAX_WRITE_IOV];
    int                     niov = 0;

    conn = request->conn;

    // move request body from request->body_chain to request->output_chain.
    buf = my_buf_chain_first(request->body_chain);
    while (buf != NULL && request->content_remain > 0) {
        n = buf->end - buf->pos;
        if (n == 0) {
            buf = my_buf_chain_next(request->body_chain, buf);
            continue;
        }

        if (n > request->content_remain) {
            n = request->content_remain;
        }

        tmp_buf = my_buf_alloc(buf->data, buf->pos, buf->pos + n);
        my_buf_chain_enqueue(request->output_chain, tmp_buf);
        my_buf_free(tmp_buf);
        todo += n;
        request->content_remain -= n;
    }
    my_buf_chain_consume(request->body_chain, todo);

    if (my_buf_chain_total_bytes(request->output_chain) == 0) {
        if (request->content_remain == 0) {
            return MY_SUCCESS;
        }

        return MY_AGAIN;
    }

    m = 0;
    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = my_buf_chain_first(request->output_chain);
        while (niov < MY_HTTP_MAX_WRITE_IOV && buf) {
            n = buf->end - buf->pos;
            if (n == 0) {
                buf = my_buf_chain_next(request->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = (size_t)n;
            niov++;
            todo += n;

            buf = my_buf_chain_next(request->output_chain, buf);
        }

        if (todo == 0) {
            break;
        }

        if (niov == 1) {
            sres = my_conn_write(conn, tiovec[0].iov_base, (int64_t)tiovec[0].iov_len);
        } else {
            sres = my_conn_writev(conn, tiovec, niov);
        }

        /* wait write event callback */
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("send response body eagain, m: %d", m);

            // if the write operation returns EAGAIN and no data is sent, then BUSY.
            if (m == 0) {
                return MY_BUSY;
            }

            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("send request body failed, errno: %d", sres.res);
            return (my_result_t)(sres.res) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("server close read in send request body");
            return MY_EOS;
        }

        my_buf_chain_consume(request->output_chain, sres.res);
        m += sres.res;
    }

    // a body of the specified length has been sent.
    if (request->content_remain == 0) {
        return MY_SUCCESS;
    }

    return MY_AGAIN;
}


static my_result_t
my_http_send_request_body(my_http_request_t *request)
{
    if (request->chunked) {
        return my_http_send_request_body_in_chunked_transfer(request);
    }

    if (request->content_length >= 0) {
        return my_http_send_request_body_in_cl_transfer(request);
    }

    // nobody
    return MY_SUCCESS;
}


static my_result_t
my_http_do_send_request_body(my_event_t *event)
{
    my_conn_t           *conn;
    my_http_request_t   *request;
    my_result_t         res;

    conn = event->data;
    request = conn->data;

    if (!request->start_send_body) {
        MY_LOG_FATAL("unexcept stats");
    }

    if (event->timedout) {
        MY_LOG_DEBUG("send request body timeout");
        event->timedout = 0;
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_TRUE);
        request->error = MY_HTTP_SEND_REQUEST_BODY_TIMEOUT;
        return MY_TIMEOUT;
    }

    if (request->http_version == MY_HTTP_VERSION_20) {
#if 1
        MY_LOG_FATAL("no implements");
        res = MY_FAILED;
#else
        res = my_h2_send_request_body(request);
#endif
    } else if (request->http_version == MY_HTTP_VERSION_QUIC) {
#if 1
        MY_LOG_FATAL("no implements");
        res = MY_FAILED;
#else
        res = my_quic_send_request_body(request);
#endif
    } else {
        res = my_http_send_request_body(request);
    }

    if (res == MY_BUSY) {
        return res;
    }

    if (res != MY_SUCCESS || res != MY_AGAIN) {
        request->error = res;
    }

    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_TRUE);

    return res;
}



static void
my_http_send_request_body_handler(my_event_t *event)
{
    my_result_t         res;
    my_conn_t           *conn;
    my_http_request_t   *request;

    conn = event->data;
    request = conn->data;

    res = my_http_do_send_request_body(event);
    // wait write event callback
    if (res == MY_BUSY) {
        my_http_enabled_send_request_body(request);
        return;
    }

    if (res == MY_SUCCESS && request->error == MY_SUCCESS) {
        request->done = 1;
    }

    request->handler(request, res);
}


void
my_http_start_send_request_body(my_http_request_t *request,
    my_http_request_handler_t notify_handler, void *data)
{
    my_conn_t           *conn;
    my_event_t          *event;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    if (!request->send_header_done) {
        MY_LOG_FATAL("request header not send");
        return;
    }

    if (request->start_send_body) {
        MY_LOG_FATAL("repeat call start_send_request_body");
        return;
    }

    request->start_send_body = 1;

    if (request->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http request already finalish");
        return;
    }

    request->handler = notify_handler;
    request->data = data;

    conn = request->conn;
    event = &conn->write;

    event->handler = my_http_send_request_body_handler;

    my_event_loop_add_timer(event, send_request_body_timeout);
    my_event_loop_enabled_io(conn, MY_TRUE);

    my_http_send_request_body_handler(event);
}


void
my_http_enabled_send_request_body(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_event_t          *event;

    conn = request->conn;
    event = &conn->write;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    if (!request->start_send_body) {
        MY_LOG_FATAL("must exec start send request body first");
        return;
    }

    if (request->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    my_event_loop_add_timer(event, send_request_body_timeout);
    my_event_loop_enabled_io(conn, MY_TRUE);

    if (event->ready) {
        event->handler(event);
    }
}


// support the following formats.
// [HTTP_VERSION][ ][HTTP_STATUS_CODE][ ][HTTP_REASION]
my_result_t
my_http_parse_response_line(my_http_response_t *response)
{
    char                *p;
    char                *end;
    my_http_request_t   *request;

    p = response->readline_sm.line.data;
    end = p + response->readline_sm.line.len;
    request = response->request;

    if (p == end || *(end - 1) != LF) {
        return MY_HTTP_INVALID_RESPONSE_LINE;
    }

    // parse http version
    my_str_t http_version_str;
    http_version_str.data = p;
    for (; p != end; p++) {
        if (*p == ' ') {
            http_version_str.len = p - http_version_str.data;
            break;
        }
    }

    if (http_version_str.len == 0) {
        return MY_HTTP_INVALID_HTTP_VERSION;
    }

    my_str_t *request_version = NULL;
    if (request->http_version == MY_HTTP_VERSION_10) {
        request_version = &my_http_version_10_str;
    } else if (request->http_version == MY_HTTP_VERSION_11) {
        request_version = &my_http_version_11_str;
    } else {
        MY_LOG_FATAL("no implements");
        return MY_FALSE;
    }

    if (http_version_str.len != request_version->len ||
        strncmp(http_version_str.data,
            request_version->data, (size_t)http_version_str.len) != 0) {
        return MY_HTTP_INVALID_HTTP_VERSION;
    }

    // parse space before status code
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    // parse status code
    int status_code = 0;
    for (; p != end; p++) {
        if (*p == ' ') {
            break;
        }

        if (*p < '0' || *p > '9') {
            return MY_HTTP_INVALID_RESPONSE_STATUS_CODE;
        }

        status_code = status_code * 10 + (*p - '0');
    }

    if (status_code < 100 || status_code > 999) {
        return MY_HTTP_INVALID_RESPONSE_STATUS_CODE;
    }

    response->status_code = status_code;

    // parse space before response reason.
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    my_str_t reason = MY_NULL_STRING;
    reason.data = p;

    for (; p != end; p++) {
        if (*p == CR || *p == LF) {
            break;
        }
    }

    response->reason = my_pool_mem_calloc(request->pool, sizeof(my_str_t));
    *response->reason = reason;

    return MY_SUCCESS;
}


static my_result_t
my_http_request_process_response_header(my_http_response_t *response)
{
    my_http_request_t   *request;
    my_http_header_t    *header;

    request = response->request;

    // 4xx, 5xx disable keepalive.
    if (response->status_code >= MY_HTTP_BAD_REQUEST) {
        request->want_close = 1;
    }

    int http10_want_close = 1;
    header = my_http_headers_get(&response->headers, &my_http_connection_hdr_name);
    if (header != NULL) {
        if (request->http_version == MY_HTTP_VERSION_10 &&
            header->value.len == MY_HTTP_KEEPALIVE_LEN &&
            strncasecmp(header->value.data, MY_HTTP_KEEPALIVE, MY_HTTP_KEEPALIVE_LEN) == 0) {
            http10_want_close = 0;
        }

        if (request->http_version >= MY_HTTP_VERSION_11 &&
            header->value.len == MY_HTTP_CLOSE_LEN &&
            strncasecmp(header->value.data, MY_HTTP_CLOSE, MY_HTTP_CLOSE_LEN) == 0) {
            request->want_close = 1;
        }
    }

    if (request->http_version == MY_HTTP_VERSION_10) {
        request->want_close = http10_want_close == 1 ? 1 : 0;
        my_http_headers_del_by_name(&response->headers, &my_http_te_hdr_name);
    }

    if (!my_http_body_allowed_for_status(response->status_code)) {
        response->nobody = 1;
    }

    int64_t content_length = -1;
    if (!response->nobody) {
        header = my_http_headers_get(&response->headers, &my_http_cl_hdr_name);
        if (header != NULL) {
            if (!my_str_to_int64(&header->value, &content_length) || content_length < 0) {
                MY_LOG_ERROR("invalid content length header");
                return MY_HTTP_INVALID_CONTENT_LENGTH_HDR;
            }

            response->content_length = content_length;
            response->content_remain = content_length;
        }

        header = my_http_headers_get(&response->headers, &my_http_te_hdr_name);
        if (header != NULL) {
            if (header->value.len == MY_HTTP_CHUNKED_LEN &&
                strncasecmp(header->value.data, MY_HTTP_CHUNKED, MY_HTTP_CHUNKED_LEN) == 0) {
                response->chunked = 1;
            } else if (header->value.len != MY_HTTP_IDENTITY_LEN ||
                strncasecmp(header->value.data, MY_HTTP_IDENTITY, MY_HTTP_IDENTITY_LEN) == 0) {
                MY_LOG_ERROR("invalid transfer-encoding header");
                return MY_HTTP_INVALID_TE_HEADER;
            }
        }
    } else {
        my_http_headers_del_by_name(&response->headers, &my_http_cl_hdr_name);
        my_http_headers_del_by_name(&response->headers, &my_http_te_hdr_name);
    }

    // if there is no Content-Length or chunked Transfer-Encoding on a response.
    // add the status is not 1xx, 204 or 304, then the body is unbounded.
    // see RFC 7230, section 3.3.
    if (!response->nobody && response->content_length < 0 && !response->chunked) {
        // unbounded body.
        request->want_close = 1;
    }

    if (response->nobody) {
        response->done = 1;
    }

    // the response body may be read when reading the response header.
    my_buf_t            *body_buf;
    body_buf = response->last_buf;
    if (body_buf && body_buf->pos != body_buf->end) {
        assert(response->body_buf == NULL);
        response->body_buf = my_buf_alloc(body_buf->data,
            body_buf->pos, body_buf->end);
    }

    response->last_buf = my_pool_mem_calloc(request->pool, sizeof(my_buf_t));
    if (body_buf) {
        // last_buf is the shadow of body_buf
        response->last_buf->pos = body_buf->pos;
    }

    return MY_SUCCESS;
}


static void
my_http_try_switch_response_header_buf(my_http_response_t *response, int64_t buf_size)
{
    my_buf_t                *header_buf;
    my_http_readline_sm_t   *readline_sm;

    header_buf = response->last_buf;
    readline_sm = &response->readline_sm;

    /* there is unparsed data, no need switch buf */
    if (header_buf && header_buf->end != header_buf->pos) {
        return;
    }

    /* avoid writing in a small space */
    if (header_buf &&
        header_buf->data_end - header_buf->end > MY_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    header_buf = my_buf_alloc_ext(buf_size);
    response->last_buf = header_buf;
    my_buf_chain_enqueue(response->header_chain, header_buf);
    my_buf_free(header_buf);

    /* copy and reset readline sm */
    strncpy(header_buf->end, readline_sm->line.data, (size_t)readline_sm->line.len);
    header_buf->end += readline_sm->line.len;
    my_http_readline_sm_reset(readline_sm);
}


static my_result_t
my_http_do_read_response_header(my_event_t *event)
{
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;
    my_http_readline_sm_t   *readline_sm;
    int64_t                 n;
    my_result_t             res;
    my_syscall_result_t     sres;
    my_http_header_t        tmp;
    my_http_header_t        *header;
    my_buf_t                *header_buf;

    conn = event->data;
    request = conn->data;
    response = request->response;

    for ( ;; ) {
        /* big http response header */
        my_http_try_switch_response_header_buf(response, response_header_buf_size);

        /* no more data, read it */
        header_buf = response->last_buf;
        if (header_buf->pos == header_buf->end) {
            n = header_buf->data_end - header_buf->end;
            sres = my_conn_read(conn, header_buf->end, n);

            /* wait read event callback */
            if (sres.eno == EAGAIN) {
                return MY_AGAIN;
            }

            if (sres.eno != 0) {
                MY_LOG_DEBUG("read response header failed, errno: %d", sres.eno);
                return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
            }

            if (sres.res == 0) {
                MY_LOG_DEBUG("server close write in read response header");
                return MY_EOS;
            }

            header_buf->end += sres.res;
        }

        readline_sm = &response->readline_sm;

        /* read line */
        res = my_http_readline(readline_sm, header_buf);
        if (res == MY_AGAIN) {
            continue;
        }

        if (res != MY_SUCCESS) {
            MY_LOG_DEBUG("invalid http response data, errcode: %d", res);
            return res;
        }

        response->header_size += readline_sm->line.len;
        if (response->header_size > max_response_header_size) {
            return MY_HTTP_RESPONSE_HEADER_TOO_LARGER;
        }

        if (my_http_empty_line(readline_sm)) {
            if (!response->parse_rl_done) {
                return MY_HTTP_INVALID_HEADER_LINE; 
            }

            response->parse_rh_done = 1;
            return MY_SUCCESS;
        }

        if (!response->parse_rl_done) {
            res = my_http_parse_response_line(response);
            if (res != MY_SUCCESS) {
                MY_LOG_DEBUG("parse response line failed, errcode: %d", res);
                return res;
            }

            response->parse_rl_done = 1;
            continue;
        }

        bzero(&tmp, sizeof(my_http_header_t));
        res = my_http_parse_header_line(&response->readline_sm.line, &tmp);
        if (res != MY_SUCCESS) {
            response->invalid_headers++;
            continue;
        }

        header = my_pool_mem_calloc(request->pool, sizeof(my_http_header_t));

        header->name = tmp.name;
        header->value = tmp.value;
        my_http_headers_add(&response->headers, header);
    }
}


static void
my_http_read_response_header_handler(my_event_t *event)
{
    my_conn_t           *conn;
    my_http_request_t   *request;
    my_http_response_t  *response;
    my_result_t         res;

    conn = event->data;
    request = conn->data;
    response = request->response;

    MY_LOG_DEBUG("http read response header handler callback");

    if (event->timedout) {
        MY_LOG_DEBUG("read response header timeout");
        event->timedout = 0;
        response->error = MY_TIMEOUT;
        /* disabled events callback */
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_FALSE);
        response->handler(request, MY_TIMEOUT);
        return;
    }

    res = my_http_do_read_response_header(event);

    /* again, wait read event callback */
    if (res == MY_AGAIN) {
        return;
    }

    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_FALSE);

    if (res != MY_SUCCESS) {
        response->error = res;
        response->handler(request, res);
        return;
    }

    res = my_http_request_process_response_header(response);
    if (res != MY_SUCCESS) {
        response->error = res;
        response->handler(request, res);
        return;
    }

    response->handler(request, MY_SUCCESS);
}


void
my_http_start_read_response_header(my_http_request_t *request,
    my_http_request_handler_t done_handler, void *data)
{
    my_conn_t           *conn;
    my_http_response_t  *response;

    conn = request->conn;
    response = request->response;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http request");
        return;
    }

    if (response->start_read_header) {
        MY_LOG_FATAL("repeat call start_read_header");
        return;
    }

    if (response->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    response->start_read_header = 1;

    response->handler = done_handler;
    response->data = data;

    conn->read.handler = my_http_read_response_header_handler;

    my_event_loop_add_timer(&conn->read, read_response_header_timeout_ms);
    my_event_loop_enabled_io(conn, MY_FALSE);

    my_http_read_response_header_handler(&conn->read);
}


static void
my_http_try_switch_response_chunk_buf(my_http_response_t *response)
{
    my_buf_t                *body_buf;
    my_buf_t                *last_buf;
    my_buf_t                *tmp_buf;
    my_buf_t                *new_buf;
    my_http_readline_sm_t   *readline_sm;
    my_http_chunk_sm_t      *chunk_sm;
    int64_t                 total_size;
    int64_t                 n;

    body_buf = response->body_buf;
    last_buf = response->body_buf;
    readline_sm = &response->readline_sm;
    chunk_sm = response->chunk_sm;

    /* there is unparsed data, no need switch buf */
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    /* avoid writeing in a small space */
    if (body_buf && body_buf->data_end - body_buf->end > MY_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    total_size = my_buf_chain_total_bytes(response->body_chain);
    if (total_size > response_body_bufs_size) {
        return;
    }

    new_buf = my_buf_alloc_ext(response_body_buf_size);
    if (body_buf) {
        switch (chunk_sm->state) {
        case SW_CHUNK_PRESTART:
        case SW_CHUNK_AFTER_DATA_CRCF:
            /* copy and reset readline sm */
            strncpy(new_buf->end, readline_sm->line.data, (size_t)readline_sm->line.len);
            new_buf->end += readline_sm->line.len;
            my_http_readline_sm_reset(readline_sm);
            break;
        case SW_CHUNK_DATA:
            if (last_buf == NULL ||
                last_buf->pos > body_buf->pos ||
                last_buf->pos < body_buf->start) {
                MY_LOG_FATAL("unreachable, the last_buf is the shadow of body_buf");
            }

            n = body_buf->pos - last_buf->pos;
            if (n > 0) {
                tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
                my_buf_chain_enqueue(response->body_chain, tmp_buf);
                my_buf_free(tmp_buf);
            }

            my_buf_free(body_buf);
            break;
        default:
            MY_LOG_FATAL("unreachable");
            break;
        }
    }

    response->body_buf = new_buf;
    if (chunk_sm->state == SW_CHUNK_DATA) {
        response->last_buf->pos = new_buf->pos;
    }
}


static void
my_http_try_flush_response_chunk_buf(my_http_response_t *response)
{
    my_buf_t                *body_buf;
    my_buf_t                *last_buf;
    my_buf_t                *tmp_buf;
    my_http_chunk_sm_t      *chunk_sm;
    int64_t                 n;

    body_buf = response->body_buf;
    last_buf = response->last_buf;
    chunk_sm = response->chunk_sm;

    if (!body_buf) {
        return;
    }

    if (chunk_sm->state != SW_CHUNK_DATA) {
        return;
    }

    if (last_buf == NULL ||
        last_buf->pos > body_buf->pos ||
        last_buf->pos < body_buf->start) {
        MY_LOG_FATAL("unreachable, the last_buf is the shadow of body buf");
    }

    n = body_buf->pos - last_buf->pos;

    if (n == 0) {
        return;
    }

    tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
    my_buf_chain_enqueue(response->body_chain, tmp_buf);
    my_buf_free(tmp_buf);

    last_buf->pos = body_buf->pos;
}


static my_result_t
my_http_read_response_body_in_chunked_transfer(my_http_request_t *request)
{
    my_conn_t               *conn;
    my_buf_t                *body_buf;
    my_http_chunk_sm_t      *chunk_sm;
    my_http_readline_sm_t   *readline_sm;
    my_result_t             res;
    my_syscall_result_t     sres;
    int64_t                 n, todo;
    my_http_chunk_state_t   state;
    my_http_response_t      *response;

    response = request->response;

    // first call.
    if (!response->chunk_sm) {
        my_http_readline_sm_reset(&response->readline_sm);
        response->chunk_sm = my_pool_mem_calloc(request->pool, sizeof(my_http_chunk_sm_t));
    }

    conn = request->conn;
    chunk_sm = response->chunk_sm;
    readline_sm = &response->readline_sm;

    for ( ;; ) {
        my_http_try_switch_response_chunk_buf(response);

        body_buf = response->body_buf;
        n = body_buf->data_end - body_buf->end;
        if (n == 0) {
            MY_LOG_DEBUG("no enough space to write request body");
            my_http_try_flush_response_chunk_buf(response);
            return MY_BUSY;
        }

        sres = my_conn_read(conn, body_buf->end, n);
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read response body eagain");
            my_http_try_flush_response_chunk_buf(response);
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read response body error, errno: %d", sres.eno);
            my_http_try_flush_response_chunk_buf(response);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read response body eos");
            my_http_try_flush_response_chunk_buf(response);
            return MY_EOS;
        }

        body_buf->end += sres.res;

        for ( ;; ) {
            state = chunk_sm->state;

            switch (state) {
            case SW_CHUNK_PRESTART:
                res = my_http_readline(readline_sm, body_buf);
                break;
            case SW_CHUNK_START:
            case SW_CHUNK_SIZE:
                res = my_http_chunk_size_parse(chunk_sm, &readline_sm->line);
                break;
            case SW_CHUNK_DATA:
                /* consume data */
                n = body_buf->end - body_buf->pos;
                todo = chunk_sm->remain;
                if (n > todo) {
                    n = todo;
                }
                body_buf->pos += n;
                chunk_sm->remain -= n;

                res = MY_AGAIN;
                if (chunk_sm->remain == 0) {
                    my_http_try_flush_response_chunk_buf(response);
                    // to next step.
                    chunk_sm->state = SW_CHUNK_AFTER_DATA_CRCF;
                    res = MY_SUCCESS;
                }

                break;
            case SW_CHUNK_AFTER_DATA_CRCF:
                res = my_http_readline(readline_sm, body_buf);
                break;
            default:
                MY_LOG_FATAL("unreachable");
                break;
            }

            if (res == MY_SUCCESS) {
                switch (state) {
                case SW_CHUNK_PRESTART:
                    my_http_chunk_sm_reset(chunk_sm);
                    chunk_sm->state = SW_CHUNK_START;
                    break;
                case SW_CHUNK_START:
                case SW_CHUNK_SIZE:
                    /* use last_buf to record the starting position of the data */
                    /* the last_buf is the shadow of body_buf */
                    response->last_buf->pos = response->body_buf->pos;
                    break;
                case SW_CHUNK_DATA:
                    my_http_readline_sm_reset(readline_sm);
                    break;
                case SW_CHUNK_AFTER_DATA_CRCF:
                    if (!my_http_empty_line(readline_sm)) {
                        return MY_HTTP_INVALID_CHUNK_LINE;
                    }

                    if (chunk_sm->size == 0) {
                        response->read_body_done = 1;
                        return MY_SUCCESS;
                    }

                    my_http_chunk_sm_reset(chunk_sm);
                    my_http_readline_sm_reset(readline_sm);
                    break;
                default:
                    MY_LOG_FATAL("unreachable");
                    break;
                }

                continue;
            }

            if (res == MY_AGAIN) {
                break;
            }

            /* error */
            return res;
        }
    }

}


static void
my_http_try_switch_response_body_buf(my_http_response_t *response)
{
    my_buf_t                *body_buf;
    my_buf_t                *last_buf;
    my_buf_t                *tmp_buf;
    my_buf_t                *new_buf;
    int64_t                 total_size;
    int64_t                 n;

    body_buf = response->body_buf;
    last_buf = response->last_buf;

    /* there is unparsed data, no need switch buf */
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    /* avoid wrieing in a small space */
    if (body_buf && body_buf->data_end - body_buf->end > MY_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    total_size = my_buf_chain_total_bytes(response->body_chain);
    if (total_size > response_body_bufs_size) {
        return;
    }

    new_buf = my_buf_alloc_ext(response_body_buf_size);

    if (body_buf) {
        if (last_buf == NULL ||
            last_buf->pos > body_buf->pos ||
            last_buf->pos < body_buf->start) {
            MY_LOG_FATAL("unreachable, the last_buf is the shadow of body_buf");
        }

        n =  body_buf->pos - last_buf->pos;
        if (n > 0) {
            tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
            my_buf_chain_enqueue(response->body_chain, tmp_buf);
            my_buf_free(tmp_buf);
        }

        my_buf_free(body_buf);
    }

    response->body_buf = new_buf;
    response->last_buf->pos = new_buf->pos;
}


static void
my_http_try_flush_response_body_buf(my_http_response_t *response)
{
    my_buf_t    *body_buf;
    my_buf_t    *last_buf;
    my_buf_t    *tmp_buf;
    int64_t     n;

    body_buf = response->body_buf;
    last_buf = response->last_buf;

    if (!body_buf) {
        return;
    }

    if (last_buf == NULL ||
        last_buf->pos > body_buf->pos ||
        last_buf->pos < body_buf->start) {
        MY_LOG_FATAL("unreachable, the last_buf is the shadow of body_buf");
    }

    n = body_buf->pos - last_buf->pos;

    if (n == 0) {
        return;
    }

    tmp_buf = my_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
    my_buf_chain_enqueue(response->body_chain, tmp_buf);
    my_buf_free(tmp_buf);

    last_buf->pos = body_buf->pos;
}


static my_result_t
my_http_read_after_response_body_line(my_http_response_t *response)
{
    char                *p;
    my_buf_t            *body_buf;
    my_conn_t           *conn;
    my_syscall_result_t sres;
    my_http_request_t   *request;

    request = response->request;
    conn = request->conn;

    if (response->body_buf == NULL) {
        response->body_buf = my_buf_alloc_ext(CRLF_LEN);
    }

    if (response->body_buf->data_end - response->body_buf->pos < CRLF_LEN) {
        body_buf = my_buf_alloc_ext(CRLF_LEN);
        p = response->body_buf->pos;
        while (p != response->body_buf->end) {
            *response->body_buf->end ++ = *p ++;
        }

        my_buf_free(response->body_buf);
        response->body_buf = body_buf;
    }

    body_buf = response->body_buf;
    while (body_buf->end - body_buf->pos < CRLF_LEN) {
        sres = my_conn_read(conn, body_buf->end,
            CRLF_LEN - (body_buf->end - body_buf->pos));
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read after body line eagain");
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read after body line error, errno: %d", sres.res);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read after body line eos");
            return MY_EOS;
        }

        body_buf->end += sres.res;
    }

    if (body_buf->end - body_buf->pos < CRLF_LEN) {
        MY_LOG_FATAL("unreachable");
    }

    if (body_buf->pos[0] == CR && body_buf->pos[1] == LF) {
        body_buf->pos += CRLF_LEN;
        return MY_SUCCESS;
    }

    MY_LOG_DEBUG("read after body line failed, except %c%c, but got %c%c",
        CR, LF, body_buf->pos[0], body_buf->pos[1]);
    return MY_FAILED;
}


static my_result_t
my_http_read_response_body_in_cl_transfer(my_http_request_t *request)
{
    int64_t                 n;
    int64_t                 consum;
    my_conn_t               *conn;
    my_buf_t                *body_buf;
    my_syscall_result_t     sres;
    my_http_response_t      *response;

    conn = request->conn;
    response = request->response;

    for ( ;; ) {
        if (response->content_remain == 0) {
            if (!response->read_body_done) {
                MY_LOG_DEBUG("read response body done");
                response->read_body_done = 1;
                my_http_try_flush_response_body_buf(response);
            }

            return my_http_read_after_response_body_line(response);
        }

        my_http_try_switch_response_body_buf(response);

        body_buf = response->body_buf;
        n = body_buf->data_end - body_buf->end;

        if (n == 0) {
            MY_LOG_DEBUG("no enough space to write response body");
            my_http_try_flush_response_body_buf(response);
            return MY_BUSY;
        }

        if (n > response->content_remain + CRLF_LEN) {
            n = response->content_remain + CRLF_LEN;
        }

        sres = my_conn_read(conn, body_buf->end, n);
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read response body eagain");
            my_http_try_flush_response_body_buf(response);
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read response body error, errno: %d", sres.eno);
            my_http_try_flush_response_body_buf(response);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read response body eos");
            my_http_try_flush_response_body_buf(response);
            return MY_EOS;
        }

        body_buf->end += sres.res;

        consum = sres.res;
        if (consum > response->content_remain) {
            consum = response->content_remain;
        }

        response->content_remain -= consum;
        response->body_buf->pos += consum;
    }
}


static my_result_t
my_http_read_response_body_in_unbounded_transfer(my_http_request_t *request)
{
    int64_t                 n;
    my_conn_t               *conn;
    my_buf_t                *body_buf;
    my_syscall_result_t     sres;
    my_http_response_t      *response;

    conn = request->conn;
    response = request->response;

    for ( ;; ) {
        my_http_try_switch_response_body_buf(response);

        body_buf = response->body_buf;
        n = body_buf->data_end - body_buf->end;

        if (n == 0) {
            MY_LOG_DEBUG("no enough space to write response body");
            my_http_try_flush_response_body_buf(response);
            return MY_BUSY;
        }

        sres = my_conn_read(conn, body_buf->end, n);
        if (sres.eno == EAGAIN) {
            MY_LOG_DEBUG("read response body eagain");
            my_http_try_flush_response_body_buf(response);
            return MY_AGAIN;
        }

        if (sres.eno != 0) {
            MY_LOG_DEBUG("read response body error, errno: %d", sres.eno);
            my_http_try_flush_response_body_buf(response);
            return (my_result_t)(sres.eno) + MY_SYSTEM_ERR_CODE_BEGIN;
        }

        if (sres.res == 0) {
            MY_LOG_DEBUG("read response body eos");
            my_http_try_flush_response_body_buf(response);
            return MY_EOS;
        }

        body_buf->end += sres.res;
        response->body_buf->pos += sres.res;
    }
}


static my_result_t
my_http_do_read_response_body(my_event_t *event)
{
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;
    my_result_t             res;

    conn = event->data;
    request = conn->data;
    response = request->response;

    if (!response->start_read_body) {
        MY_LOG_FATAL("unexcept stats");
    }

    if (event->timedout) {
        MY_LOG_DEBUG("read response body timedout");
        event->timedout = 0;
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_FALSE);

        if (response->read_body_done) {
            response->error = MY_HTTP_READ_AFTER_BODY_DATA_TIMEOUT;
            request->want_close = 1;
            return MY_SUCCESS;
        }

        response->error = MY_HTTP_READ_REQUEST_BODY_TIMEOUT;
        return MY_TIMEOUT;
    }

    if (response->chunked) {
        res = my_http_read_response_body_in_chunked_transfer(request);
    } else if (response->content_length >= 0) {
        res = my_http_read_response_body_in_cl_transfer(request);
    } else {
        // unbounded body.
        res = my_http_read_response_body_in_unbounded_transfer(request);
    }

    // disabled io / MY_SUCCESS/ MY_BUSY/ MY_EOS/ MY_FAILED.
    // although there are no disable events in the MY_AGAIN scenario, 
    // we still need to display call my_http_enabled_read_response_body,
    // because the upper state machine may call my_http_disabled_read_response_body
    // to close the event notification.
    if (res != MY_AGAIN) {
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_FALSE);
    }

    if (res == MY_EOS || res == MY_FAILED) {
        // some server may not send the CRLF or chunk tailer after response body.
        if (response->read_body_done) {
            response->error = MY_HTTP_READ_AFTER_BODY_DATA_FAILED;
            request->want_close = 1;
            return MY_SUCCESS;
        }
    }

    return res;
}


static void
my_http_read_response_body_handler(my_event_t *event)
{
    my_result_t             res;
    my_conn_t               *conn;
    my_http_request_t       *request;
    my_http_response_t      *response;

    conn = event->data;
    request = conn->data;
    response = request->response;

    res = my_http_do_read_response_body(event);

    /* in again scenario, no body data, no callback needed */
    if (res == MY_AGAIN &&
        my_buf_chain_total_bytes(response->body_chain) == 0) {
        my_http_enabled_read_response_body(request);
        return;
    }

    if (res == MY_SUCCESS && response->error == MY_SUCCESS) {
        response->done = 1;
    }

    response->handler(request, res);
}


void
my_http_start_read_response_body(my_http_request_t *request,
    my_http_request_handler_t notify_handler, void *data)
{
    my_conn_t           *conn;
    my_event_t          *event;
    my_http_response_t  *response;

    response = request->response;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    if (!response->parse_rh_done) {
        MY_LOG_FATAL("header has no been parsed");
        return;
    }

    if (response->start_read_body) {
        MY_LOG_FATAL("repeat call start_read_request_body");
        return;
    }

    response->start_read_body = 1;

    if (response->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    response->handler = notify_handler;
    response->data = data;

    conn = request->conn;
    event = &conn->read;

    if (response->nobody) {
        response->read_body_done = 1;
        response->handler(request, MY_SUCCESS);
        return;
    }

    event->handler = my_http_read_response_body_handler;
    my_event_loop_add_timer(event, read_response_body_timeout);
    my_event_loop_enabled_io(conn, MY_FALSE);

    my_http_read_response_body_handler(event);
}


void
my_http_enabled_read_response_body(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_event_t          *event;
    my_http_response_t  *response;

    conn = request->conn;
    event = &conn->read;

    response = request->response;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    if (!response->start_read_body) {
        MY_LOG_FATAL("must exec start read response body first");
        return;
    }

    if (response->error != MY_SUCCESS ||
        request->terminated ||
        request->finalized) {
        MY_LOG_FATAL("the http response already finalish");
        return;
    }

    my_event_loop_add_timer(event, read_response_body_timeout);
    my_event_loop_enabled_io(conn, MY_FALSE);

    if (event->ready) {
        event->handler(event);
    }
}


void
my_http_disabled_read_response_body(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_event_t          *event;
    my_http_response_t  *response;

    conn = request->conn;
    event = &conn->read;
    response = request->response;

    if (!request->client_request) {
        MY_LOG_FATAL("current request is not http client request");
        return;
    }

    if (!response->start_read_body) {
        MY_LOG_FATAL("must exec start read response body first");
        return;
    }

    my_event_loop_del_timer(event);
    my_event_loop_disabled_io(conn, MY_FALSE);
}


// abort http request and cancel events callback
void
my_http_abort_http_request(my_http_request_t *request)
{
    my_http_response_t  *response;
    my_conn_t           *conn;

    conn = request->conn;
    response = request->response;

    request->counter = 0;
    request->terminated = 1;
    request->handler = NULL;
    request->data = NULL;
    response->handler = NULL;
    response->data = NULL;

    my_event_loop_del_timer(&conn->read);
    my_event_loop_disabled_io(conn, MY_FALSE);
    my_event_loop_del_timer(&conn->write);
    my_event_loop_disabled_io(conn, MY_TRUE);
}


static void
my_http_server_request_conn_release(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_buf_t            *pre_buf;
    conn = request->conn;

    // release
    request->conn = NULL;
    conn->data = NULL;

    // keepalive
    if (conn->server_handler != NULL &&
        !request->want_close &&
        !request->terminated &&
        !conn->read.error &&
        !conn->write.error) {

        pre_buf = request->last_buf;
        // there is remaining data, possibly a http pipline request
        if (pre_buf->end - pre_buf->pos > 0) {
            conn->data = pre_buf;
            my_buf_hold(pre_buf);
            // wait for event system callback
            conn->read.handler = conn->server_handler;
            conn->read.ready = 1;
            my_event_loop_enabled_io(conn, MY_FALSE);
            return;
        }

        my_http_server_session_pool_put(conn, server_request_keepalive_timeout);
        return;
    }

    my_http_server_conn_free_handler(conn);
}


static void
my_http_finalize_server_request_done(my_http_request_t *request)
{
    my_conn_t           *conn;

    conn = request->conn;
    conn->data = NULL;

    if (request->counter > 0) {
        return;
    }

    my_http_server_request_conn_release(request);
    return request->finalized_handler(request, MY_SUCCESS);
}

static void
my_http_finalize_server_lingering_close(my_http_request_t *request, my_result_t res)
{
    my_conn_t               *conn;
    conn = request->conn;

    (void)res;
    request->counter--;
    my_event_loop_del_timer(&conn->read);
    my_event_loop_disabled_io(conn, MY_FALSE);
    my_http_finalize_server_request_done(request);
}


static void
my_http_lingering_close_handler(my_event_t *event)
{
    my_http_request_t       *request;
    my_conn_t               *conn;
    my_buf_t                *buf;
    int64_t                 n;
    my_syscall_result_t     sres;

    conn = event->data;
    request = conn->data;

    if (event->timedout) {
        event->timedout = 0;
        /* disabled events callback */
        my_event_loop_del_timer(event);
        my_event_loop_disabled_io(conn, MY_TRUE);
        request->handler(request, MY_SUCCESS);
        return;
    }

    buf = request->body_buf;

    for ( ;; ) {
        n = buf->data_end - buf->start;
        sres = my_conn_read(conn, buf->start, n);

        /* wait read event callback */
        if (sres.eno == EAGAIN) {
            return;
        }

        if (sres.eno != 0) {
            request->handler(request, MY_SUCCESS);
            return;
        }

        if (sres.res == 0) {
            request->handler(request, MY_SUCCESS);
            return;
        }

        request->lingering_close_body += sres.res;
        if (request->lingering_close_body > max_lingering_close_body) {
            /* disabled events callback */
            my_event_loop_del_timer(event);
            my_event_loop_disabled_io(conn, MY_TRUE);
            request->handler(request, MY_SUCCESS);
            return;
        }
    }
}


static void
my_http_finalize_server_request(my_http_request_t *request)
{
    my_http_response_t  *response;
    my_conn_t           *conn;

    conn = request->conn;
    response = request->response;

    // check send response body done.
    if (!response->done && response->error == MY_SUCCESS) {
        response->error = MY_HTTP_SEND_RESPONSE_TERMINATED;
    }

    // check read request body done.
    if (!request->done && request->error == MY_SUCCESS) {
        request->error = MY_HTTP_READ_REQUEST_TERMINATED;
    }

    if (request->error != MY_SUCCESS || response->error != MY_SUCCESS) {
        request->want_close = 1;
    }

    if (request->want_close &&
        enabled_lingering_close) {

        request->counter++;

        // free old body_buf
        if (request->body_buf) {
            my_buf_free(request->body_buf);
        }
        // alloc new body_buf for lingering close body.
        request->body_buf = my_buf_alloc_ext(request_body_buf_size);

        request->handler = my_http_finalize_server_lingering_close;
        conn->read.handler = my_http_lingering_close_handler;
        my_event_loop_add_timer(&conn->read, lingering_close_timeout);
        my_event_loop_enabled_io(conn, MY_FALSE);
    }

    my_http_finalize_server_request_done(request);
}


static void
my_http_client_request_conn_release(my_http_request_t *request)
{
    my_conn_t           *conn;
    my_str_t            key;

    conn = request->conn;

    // release
    request->conn = NULL;
    conn->data = NULL;

    // keepalive
    if (!request->want_close &&
        !request->terminated &&
        !conn->read.error &&
        !conn->write.error) {

        char dst[MY_SOCKADDR_SIZE];

        // TODO support prefix for shard pool
        if (!my_conn_peer_sockaddr_str_get(conn, dst, MY_SOCKADDR_SIZE)) {
            my_http_client_conn_free_handler(conn);
            return;
        }

        key.data = dst;
        key.len = (int64_t)strlen(dst);

        my_http_client_session_pool_put(&key,
            conn, client_request_keepalive_timeout);
        return;
    }

    my_http_client_conn_free_handler(conn);
}


static void
my_http_finalize_client_request_done(my_http_request_t *request)
{
    my_conn_t           *conn;

    conn = request->conn;
    conn->data = NULL;

    if (request->counter > 0) {
        return;
    }

    my_http_client_request_conn_release(request);
    return request->finalized_handler(request, MY_SUCCESS);
}


static void
my_http_finalize_client_request(my_http_request_t *request)
{
    my_http_response_t  *response;
    response = request->response;

    // check send request body done.
    if (!request->done && request->error == MY_SUCCESS) {
        request->error = MY_HTTP_SEND_REQUEST_TERMINATED;
    }

    // check read resonse body done.
    if (!response->done && request->error == MY_SUCCESS) {
        request->error = MY_HTTP_READ_RESPONSE_TERMINATED;
    }

    if (request->error != MY_SUCCESS || response->error != MY_SUCCESS) {
        request->want_close = 1;
    }

    my_http_finalize_client_request_done(request);
}


void
my_http_finalize_request(my_http_request_t *request,
    my_http_request_handler_t done_handler, void *data)
{
    my_http_response_t  *response;
    my_conn_t           *conn;

    conn = request->conn;
    response = request->response;

    if (request->finalized) {
        MY_LOG_FATAL("http request already finalized");
        return;
    }

    request->finalized = 1;
    request->finalized_handler = done_handler;
    request->data = data;
    response->handler = NULL;
    response->data = NULL;

    my_event_loop_del_timer(&conn->read);
    my_event_loop_disabled_io(conn, MY_FALSE);
    my_event_loop_del_timer(&conn->write);
    my_event_loop_disabled_io(conn, MY_TRUE);

    if (request->client_request) {
        my_http_finalize_client_request(request);
        return;
    }

    my_http_finalize_server_request(request);
}


my_http_request_t *
my_http_request_create(my_conn_t *conn, my_bool_t client_request)
{
    my_http_request_t   *request;
    my_http_response_t  *response;
    my_buf_t            *pre_buf;

    request = my_thread_calloc(sizeof(my_http_request_t));

    if (client_request) {
        request->client_request = 1;
    }

    request->header_chain = my_buf_chain_alloc();
    request->body_chain = my_buf_chain_alloc();
    request->output_chain = my_buf_chain_alloc();
    request->conn = conn;
    conn->data = request;
    request->pool = my_pool_create();
    request->error = MY_SUCCESS;
    request->content_length = -1; // invalid content length.
    my_http_headers_init(&request->headers, request->pool);

    response = my_pool_mem_calloc(request->pool, sizeof(my_http_response_t));
    response->header_chain = my_buf_chain_alloc();
    response->body_chain = my_buf_chain_alloc();
    response->output_chain = my_buf_chain_alloc();
    response->content_length = -1; // invalid content length.

    request->response = response;
    response->request = request;

    // for http pipeline. move pre_buf to request.
    if (conn->data) {
        if (client_request) {
            MY_LOG_FATAL("unreachable");
        }

        pre_buf = conn->data;
        my_buf_chain_enqueue(request->header_chain, pre_buf);
        request->last_buf = pre_buf;
        my_buf_free(pre_buf);
    }

    return request;
}


void
my_http_request_destroy(my_http_request_t *request)
{
    my_conn_t *conn = request->conn;

    my_http_response_t *response = request->response;
    my_buf_chain_free(response->header_chain);
    my_buf_chain_free(response->body_chain);
    my_buf_chain_free(response->output_chain);
    if (response->body_buf) {
        my_buf_free(response->body_buf);
    }

    my_pool_destroy(request->pool);
    my_buf_chain_free(request->header_chain);
    my_buf_chain_free(request->body_chain);
    if (request->body_buf) {
        my_buf_free(request->body_buf);
    }

    my_thread_free(request);

    if (conn) {
        my_http_server_conn_free_handler(conn);
    }
}


void
my_http_request_dump(my_http_request_t *request)
{
    MY_LOG_DEBUG("---------- http request ---------");
    MY_LOG_DEBUG("uri: %v, uri_ext: %v, args: %v, fragment: %v, http_version: %d",
        &request->uri, &request->uri_ext, &request->args,
        &request->fragment, request->http_version);
    MY_LOG_DEBUG("------- http request headers ----");
    my_http_headers_dump(&request->headers);
}


void
my_http_request_body_dump(my_http_request_t *request)
{
    my_buf_t    *buf;
    my_str_t    str;
    int64_t     n = 0;

    MY_LOG_DEBUG("--------- http body fragment ----------");

    buf = my_buf_chain_first(request->body_chain);

    while (buf != NULL) {
        str.data = buf->pos;
        str.len = buf->end - buf->pos;
        MY_LOG_DEBUG("%v", &str);

        n += str.len;

        buf = my_buf_chain_next(request->body_chain, buf);
    }

    my_buf_chain_consume(request->body_chain, n);

    MY_LOG_DEBUG("---------------------------------------");
}